API 0.9.5
Foundation/CPKeyValueCoding.j
Go to the documentation of this file.
00001 /*
00002  * CPKeyValueCoding.j
00003  * Foundation
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 CPUndefinedKeyException     = @"CPUndefinedKeyException";
00026 CPTargetObjectUserInfoKey   = @"CPTargetObjectUserInfoKey";
00027 CPUnknownUserInfoKey        = @"CPUnknownUserInfoKey";
00028 
00029 var CPObjectAccessorsForClassKey            = @"$CPObjectAccessorsForClassKey",
00030     CPObjectModifiersForClassKey            = @"$CPObjectModifiersForClassKey";
00031 
00032 @implementation CPObject (CPKeyValueCoding)
00033 
00034 + (BOOL)accessInstanceVariablesDirectly
00035 {
00036     return YES;
00037 }
00038 
00039 - (id)valueForKey:(CPString)aKey
00040 {
00041     var theClass = [self class],
00042         accessor = nil,
00043         accessors = theClass[CPObjectAccessorsForClassKey];
00044 
00045     if (!accessors)
00046         accessors = theClass[CPObjectAccessorsForClassKey] = { };
00047 
00048     if (accessors.hasOwnProperty(aKey))
00049         accessor = accessors[aKey];
00050 
00051     else
00052     {
00053         var string = nil,
00054             capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1),
00055             underscoreKey = nil,
00056             isKey = nil;
00057 
00058         // First search for accessor methods of the form -get<Key>, -<key>, -is<Key>
00059         // (the underscore versions are deprecated)
00060         if ([theClass instancesRespondToSelector:string = sel_getUid("get" + capitalizedKey)] ||
00061             [theClass instancesRespondToSelector:string = sel_getUid(aKey)] ||
00062             [theClass instancesRespondToSelector:string = sel_getUid((isKey = "is" + capitalizedKey))] ||
00063             //FIXME: is deprecated in Cocoa 10.3
00064             [theClass instancesRespondToSelector:string = sel_getUid("_get" + capitalizedKey)] ||
00065             //FIXME: is deprecated in Cocoa 10.3
00066             [theClass instancesRespondToSelector:string = sel_getUid((underscoreKey = "_" + aKey))] ||
00067             //FIXME: was NEVER supported by Cocoa
00068             [theClass instancesRespondToSelector:string = sel_getUid("_" + isKey)])
00069             accessor = accessors[aKey] = [0, string];
00070 
00071         else if ([theClass instancesRespondToSelector:sel_getUid("countOf" + capitalizedKey)])
00072         {
00073             // Otherwise, search for ordered to-many relationships:
00074             // -countOf<Key> and either of -objectIn<Key>atIndex: or -<key>AtIndexes:.
00075             if ([theClass instancesRespondToSelector:sel_getUid("objectIn" + capitalizedKey + "AtIndex:")] ||
00076                 [theClass instancesRespondToSelector:sel_getUid(aKey + "AtIndexes:")])
00077                 accessor = accessors[aKey] = [1];
00078 
00079             // Otherwise, search for unordered to-many relationships
00080             // -countOf<Key>, -enumeratorOf<Key>, and -memberOf<Key>:.
00081             else if ([theClass instancesRespondToSelector:sel_getUid("enumeratorOf" + capitalizedKey)] &&
00082                     [theClass instancesRespondToSelector:sel_getUid("memberOf" + capitalizedKey + ":")])
00083                 accessor = accessors[aKey] = [2];
00084         }
00085 
00086         if (!accessor)
00087         {
00088             // Otherwise search for instance variable: _<key>, _is<Key>, key, is<Key>
00089             if (class_getInstanceVariable(theClass, string = underscoreKey) ||
00090                 class_getInstanceVariable(theClass, string = "_" + isKey) ||
00091                 class_getInstanceVariable(theClass, string = aKey) ||
00092                 class_getInstanceVariable(theClass, string = isKey))
00093                 accessor = accessors[aKey] = [3, string];
00094 
00095             // Otherwise return valueForUndefinedKey:
00096             else
00097                 accessor = accessors[aKey] = [];
00098         }
00099     }
00100 
00101     switch (accessor[0])
00102     {
00103         case 0:     return objj_msgSend(self, accessor[1]);
00104                     // FIXME: We shouldn't be creating a new one every time.
00105         case 1:     return [[_CPKeyValueCodingArray alloc] initWithTarget:self key:aKey];
00106                     // FIXME: We shouldn't be creating a new one every time.
00107         case 2:     return [[_CPKeyValueCodingSet alloc] initWithTarget:self key:aKey];
00108         case 3:     if ([theClass accessInstanceVariablesDirectly])
00109                         return self[accessor[1]];
00110     }
00111 
00112     return [self valueForUndefinedKey:aKey];
00113 }
00114 
00115 - (id)valueForKeyPath:(CPString)aKeyPath
00116 {
00117     var firstDotIndex = aKeyPath.indexOf(".");
00118 
00119     if (firstDotIndex === CPNotFound)
00120         return [self valueForKey:aKeyPath];
00121 
00122     var firstKeyComponent = aKeyPath.substring(0, firstDotIndex),
00123         remainingKeyPath = aKeyPath.substring(firstDotIndex + 1),
00124         value = [self valueForKey:firstKeyComponent];
00125 
00126     return [value valueForKeyPath:remainingKeyPath];
00127 }
00128 
00129 - (CPDictionary)dictionaryWithValuesForKeys:(CPArray)keys
00130 {
00131     var index = 0,
00132         count = keys.length,
00133         dictionary = [CPDictionary dictionary];
00134 
00135     for (; index < count; ++index)
00136     {
00137         var key = keys[index],
00138             value = [self valueForKey:key];
00139 
00140         if (value === nil)
00141             [dictionary setObject:[CPNull null] forKey:key];
00142 
00143         else
00144             [dictionary setObject:value forKey:key];
00145     }
00146 
00147     return dictionary;
00148 }
00149 
00150 - (id)valueForUndefinedKey:(CPString)aKey
00151 {
00152     [[CPException exceptionWithName:CPUndefinedKeyException
00153                             reason:[self description] + " is not key value coding-compliant for the key " + aKey
00154                           userInfo:[CPDictionary dictionaryWithObjects:[self, aKey] forKeys:[CPTargetObjectUserInfoKey, CPUnknownUserInfoKey]]] raise];
00155 }
00156 
00157 - (void)setValue:(id)aValue forKeyPath:(CPString)aKeyPath
00158 {
00159     if (!aKeyPath)
00160         aKeyPath = @"self";
00161 
00162     var firstDotIndex = aKeyPath.indexOf(".");
00163 
00164     if (firstDotIndex === CPNotFound)
00165         return [self setValue:aValue forKey:aKeyPath];
00166 
00167     var firstKeyComponent = aKeyPath.substring(0, firstDotIndex),
00168         remainingKeyPath = aKeyPath.substring(firstDotIndex + 1),
00169         value = [self valueForKey:firstKeyComponent];
00170 
00171     return [value setValue:aValue forKeyPath:remainingKeyPath];
00172 }
00173 
00174 - (void)setValue:(id)aValue forKey:(CPString)aKey
00175 {
00176     var theClass = [self class],
00177         modifier = nil,
00178         modifiers = theClass[CPObjectModifiersForClassKey];
00179 
00180     if (!modifiers)
00181         modifiers = theClass[CPObjectModifiersForClassKey] = { };
00182 
00183     if (modifiers.hasOwnProperty(aKey))
00184         modifier = modifiers[aKey];
00185 
00186     else
00187     {
00188         var string = nil,
00189             capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1),
00190             isKey = nil;
00191 
00192         if ([theClass instancesRespondToSelector:string = sel_getUid("set" + capitalizedKey + ":")] ||
00193             //FIXME: deprecated in Cocoa 10.3
00194             [theClass instancesRespondToSelector:string = sel_getUid("_set" + capitalizedKey + ":")])
00195             modifier = modifiers[aKey] = [0, string];
00196 
00197         else if (class_getInstanceVariable(theClass, string = "_" + aKey) ||
00198             class_getInstanceVariable(theClass, string = "_" + (isKey = "is" + capitalizedKey)) ||
00199             class_getInstanceVariable(theClass, string = aKey) ||
00200             class_getInstanceVariable(theClass, string = isKey))
00201             modifier = modifiers[aKey] = [1, string];
00202 
00203         else
00204             modifier = modifiers[aKey] = [];
00205     }
00206 
00207     switch (modifier[0])
00208     {
00209         case 0:     return objj_msgSend(self, modifier[1], aValue);
00210 
00211         case 1:     if ([theClass accessInstanceVariablesDirectly])
00212                     {
00213                         [self willChangeValueForKey:aKey];
00214 
00215                         self[modifier[1]] = aValue;
00216 
00217                         return [self didChangeValueForKey:aKey];
00218                     }
00219     }
00220 
00221     return [self setValue:aValue forUndefinedKey:aKey];
00222 
00223 }
00224 
00225 - (void)setValuesForKeysWithDictionary:(CPDictionary)keyedValues
00226 {
00227     var value,
00228         key,
00229         keyEnumerator = [keyedValues keyEnumerator];
00230 
00231     while (key = [keyEnumerator nextObject])
00232     {
00233         value = [keyedValues objectForKey: key];
00234 
00235         if (value === [CPNull null])
00236             [self setValue: nil forKey: key];
00237 
00238         else
00239             [self setValue: value forKey: key];
00240     }
00241 }
00242 
00243 - (void)setValue:(id)aValue forUndefinedKey:(CPString)aKey
00244 {
00245     [[CPException exceptionWithName:CPUndefinedKeyException
00246                             reason:[self description] + " is not key value coding-compliant for the key " + aKey
00247                           userInfo:[CPDictionary dictionaryWithObjects:[self, aKey] forKeys:[CPTargetObjectUserInfoKey, CPUnknownUserInfoKey]]] raise];
00248 }
00249 
00250 @end
00251 
00252 @implementation CPDictionary (CPKeyValueCoding)
00253 
00254 - (id)valueForKey:(CPString)aKey
00255 {
00256     if ([aKey hasPrefix:@"@"])
00257         return [super valueForKey:aKey.substr(1)];
00258 
00259     return [self objectForKey:aKey];
00260 }
00261 
00262 - (void)setValue:(id)aValue forKey:(CPString)aKey
00263 {
00264     if (aValue !== nil)
00265         [self setObject:aValue forKey:aKey];
00266 
00267     else
00268         [self removeObjectForKey:aKey];
00269 }
00270 
00271 @end
00272 
00273 @implementation CPNull (CPKeyValueCoding)
00274 
00275 - (id)valueForKey:(CPString)aKey
00276 {
00277     return self;
00278 }
00279 
00280 @end
00281 
00282 @implementation _CPKeyValueCodingArray : CPArray
00283 {
00284     id  _target;
00285 
00286     SEL _countOfSelector;
00287     SEL _objectInAtIndexSelector;
00288     SEL _atIndexesSelector;
00289 }
00290 
00291 - (id)initWithTarget:(id)aTarget key:(CPString)aKey
00292 {
00293     self = [super init];
00294 
00295     if (self)
00296     {
00297         var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1);
00298 
00299         _target = aTarget;
00300 
00301         _countOfSelector = CPSelectorFromString("countOf" + capitalizedKey);
00302 
00303         _objectInAtIndexSelector = CPSelectorFromString("objectIn" + capitalizedKey + "AtIndex:");
00304 
00305         if (![_target respondsToSelector:_objectInAtIndexSelector])
00306             _objectInAtIndexSelector = nil;
00307 
00308         _atIndexesSelector = CPSelectorFromString(aKey + "AtIndexes:");
00309 
00310         if (![_target respondsToSelector:_atIndexesSelector])
00311             _atIndexesSelector = nil;
00312     }
00313 
00314     return self;
00315 }
00316 
00317 - (CPUInteger)count
00318 {
00319     return objj_msgSend(_target, _countOfSelector);
00320 }
00321 
00322 - (id)objectAtIndex:(CPUInteger)anIndex
00323 {
00324     if (_objectInAtIndexSelector)
00325         return objj_msgSend(_target, _objectInAtIndexSelector, anIndex);
00326 
00327     return objj_msgSend(_target, _atIndexesSelector, [CPIndexSet indexSetWithIndex:anIndex])[0];
00328 }
00329 
00330 - (CPArray)objectsAtIndexes:(CPIndexSet)indexes
00331 {
00332     if (_atIndexesSelector)
00333         return objj_msgSend(_target, _atIndexesSelector, indexes);
00334 
00335     return [super objectsAtIndexes:indexes];
00336 }
00337 
00338 - (Class)classForCoder
00339 {
00340     return [CPArray class];
00341 }
00342 
00343 - (id)copy
00344 {
00345     // We do this to ensure we return a CPArray.
00346     return [CPArray arrayWithArray:self];
00347 }
00348 
00349 @end
00350 
00351 @implementation _CPKeyValueCodingSet : CPSet
00352 {
00353     id  _target;
00354 
00355     SEL _countOfSelector;
00356     SEL _enumeratorOfSelector;
00357     SEL _memberOfSelector;
00358 }
00359 
00360 // This allows things like setByAddingObject: to work (since they use [[self class] alloc] internally).
00361 - (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
00362 {
00363     return [[CPSet alloc] initWithObjects:objects count:aCount];
00364 }
00365 
00366 - (id)initWithTarget:(id)aTarget key:(CPString)aKey
00367 {
00368     self = [super initWithObjects:nil count:0];
00369 
00370     if (self)
00371     {
00372         var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1);
00373 
00374         _target = aTarget;
00375 
00376         _countOfSelector = CPSelectorFromString("countOf" + capitalizedKey);
00377         _enumeratorOfSelector = CPSelectorFromString("enumeratorOf" + capitalizedKey);
00378         _memberOfSelector = CPSelectorFromString("memberOf" + capitalizedKey + ":");
00379     }
00380 
00381     return self;
00382 }
00383 
00384 - (CPUInteger)count
00385 {
00386     return objj_msgSend(_target, _countOfSelector);
00387 }
00388 
00389 - (CPEnumerator)objectEnumerator
00390 {
00391     return objj_msgSend(_target, _enumeratorOfSelector);
00392 }
00393 
00394 - (id)member:(id)anObject
00395 {
00396     return objj_msgSend(_target, _memberOfSelector, anObject);
00397 }
00398 
00399 - (Class)classForCoder
00400 {
00401     return [CPSet class];
00402 }
00403 
00404 - (id)copy
00405 {
00406     // We do this to ensure we return a CPSet.
00407     return [CPSet setWithSet:self];
00408 }
00409 
00410 @end
00411 
 All Classes Files Functions Variables Defines