![]() |
API 0.9.5
|
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