API  1.0.0
CPKeyValueCoding.j
Go to the documentation of this file.
1 /*
2  * CPKeyValueCoding.j
3  * Foundation
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 CPUndefinedKeyException = @"CPUndefinedKeyException";
26 CPTargetObjectUserInfoKey = @"CPTargetObjectUserInfoKey";
27 CPUnknownUserInfoKey = @"CPUnknownUserInfoKey";
28 
29 var CPObjectAccessorsForClassKey = @"$CPObjectAccessorsForClassKey",
30  CPObjectModifiersForClassKey = @"$CPObjectModifiersForClassKey";
31 
33 
34 + (BOOL)accessInstanceVariablesDirectly
35 {
36  return YES;
37 }
38 
39 - (id)valueForKey:(CPString)aKey
40 {
41  var theClass = [self class],
42  accessor = nil,
43  accessors = theClass[CPObjectAccessorsForClassKey];
44 
45  if (!accessors)
46  accessors = theClass[CPObjectAccessorsForClassKey] = { };
47 
48  if (accessors.hasOwnProperty(aKey))
49  accessor = accessors[aKey];
50 
51  else
52  {
53  var string = nil,
54  capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1),
55  underscoreKey = nil,
56  isKey = nil;
57 
58  // First search for accessor methods of the form -get<Key>, -<key>, -is<Key>
59  // (the underscore versions are deprecated)
60  if ([theClass instancesRespondToSelector:string = sel_getUid("get" + capitalizedKey)] ||
61  [theClass instancesRespondToSelector:string = sel_getUid(aKey)] ||
62  [theClass instancesRespondToSelector:string = sel_getUid((isKey = "is" + capitalizedKey))] ||
63  //FIXME: is deprecated in Cocoa 10.3
64  [theClass instancesRespondToSelector:string = sel_getUid("_get" + capitalizedKey)] ||
65  //FIXME: is deprecated in Cocoa 10.3
66  [theClass instancesRespondToSelector:string = sel_getUid((underscoreKey = "_" + aKey))] ||
67  //FIXME: was NEVER supported by Cocoa
68  [theClass instancesRespondToSelector:string = sel_getUid("_" + isKey)])
69  accessor = accessors[aKey] = [0, string];
70 
71  else if ([theClass instancesRespondToSelector:sel_getUid("countOf" + capitalizedKey)])
72  {
73  // Otherwise, search for ordered to-many relationships:
74  // -countOf<Key> and either of -objectIn<Key>atIndex: or -<key>AtIndexes:.
75  if ([theClass instancesRespondToSelector:sel_getUid("objectIn" + capitalizedKey + "AtIndex:")] ||
76  [theClass instancesRespondToSelector:sel_getUid(aKey + "AtIndexes:")])
77  accessor = accessors[aKey] = [1];
78 
79  // Otherwise, search for unordered to-many relationships
80  // -countOf<Key>, -enumeratorOf<Key>, and -memberOf<Key>:.
81  else if ([theClass instancesRespondToSelector:sel_getUid("enumeratorOf" + capitalizedKey)] &&
82  [theClass instancesRespondToSelector:sel_getUid("memberOf" + capitalizedKey + ":")])
83  accessor = accessors[aKey] = [2];
84  }
85 
86  if (!accessor)
87  {
88  // Otherwise search for instance variable: _<key>, _is<Key>, key, is<Key>
89  if (class_getInstanceVariable(theClass, string = underscoreKey) ||
90  class_getInstanceVariable(theClass, string = "_" + isKey) ||
91  class_getInstanceVariable(theClass, string = aKey) ||
92  class_getInstanceVariable(theClass, string = isKey))
93  accessor = accessors[aKey] = [3, string];
94 
95  // Otherwise return valueForUndefinedKey:
96  else
97  accessor = accessors[aKey] = [];
98  }
99  }
100 
101  switch (accessor[0])
102  {
103  case 0:
104  return self.isa.objj_msgSend0(self, accessor[1]);
105 
106  case 1:
107  // FIXME: We shouldn't be creating a new one every time.
108  return [[_CPKeyValueCodingArray alloc] initWithTarget:self key:aKey];
109 
110  case 2:
111  // FIXME: We shouldn't be creating a new one every time.
112  return [[_CPKeyValueCodingSet alloc] initWithTarget:self key:aKey];
113 
114  case 3:
115  if ([theClass accessInstanceVariablesDirectly])
116  return self[accessor[1]];
117  }
118 
119  return [self valueForUndefinedKey:aKey];
120 }
121 
122 - (id)valueForKeyPath:(CPString)aKeyPath
123 {
124  var firstDotIndex = aKeyPath.indexOf(".");
125 
126  if (firstDotIndex === CPNotFound)
127  return [self valueForKey:aKeyPath];
128 
129  var firstKeyComponent = aKeyPath.substring(0, firstDotIndex),
130  remainingKeyPath = aKeyPath.substring(firstDotIndex + 1),
131  value = [self valueForKey:firstKeyComponent];
132 
133  return [value valueForKeyPath:remainingKeyPath];
134 }
135 
136 - (CPDictionary)dictionaryWithValuesForKeys:(CPArray)keys
137 {
138  var index = 0,
139  count = keys.length,
140  dictionary = @{};
141 
142  for (; index < count; ++index)
143  {
144  var key = keys[index],
145  value = [self valueForKey:key];
146 
147  if (value === nil)
148  [dictionary setObject:[CPNull null] forKey:key];
149 
150  else
151  [dictionary setObject:value forKey:key];
152  }
153 
154  return dictionary;
155 }
156 
157 - (id)valueForUndefinedKey:(CPString)aKey
158 {
159  [[CPException exceptionWithName:CPUndefinedKeyException
160  reason:[self _objectDescription] + " is not key value coding-compliant for the key " + aKey
161  userInfo:@{ CPTargetObjectUserInfoKey: self, CPUnknownUserInfoKey: aKey }] raise];
162 }
163 
164 - (void)setValue:(id)aValue forKeyPath:(CPString)aKeyPath
165 {
166  if (!aKeyPath)
167  aKeyPath = @"self";
168 
169  var firstDotIndex = aKeyPath.indexOf(".");
170 
171  if (firstDotIndex === CPNotFound)
172  return [self setValue:aValue forKey:aKeyPath];
173 
174  var firstKeyComponent = aKeyPath.substring(0, firstDotIndex),
175  remainingKeyPath = aKeyPath.substring(firstDotIndex + 1),
176  value = [self valueForKey:firstKeyComponent];
177 
178  return [value setValue:aValue forKeyPath:remainingKeyPath];
179 }
180 
181 - (void)setValue:(id)aValue forKey:(CPString)aKey
182 {
183  // setValue:forKey: should unwrap CPValue by default. In Objective-C we would need to care about which type
184  // the setter takes (or target ivar) and send [aValue rectValue], [aValue pointValue] etc, but in
185  // Objective-C we can use them interchangably.
186  if (aValue && aValue.isa && [aValue isKindOfClass:CPValue])
187  aValue = [aValue JSObject];
188 
189  var theClass = [self class],
190  modifier = nil,
191  modifiers = theClass[CPObjectModifiersForClassKey];
192 
193  if (!modifiers)
194  modifiers = theClass[CPObjectModifiersForClassKey] = { };
195 
196  if (modifiers.hasOwnProperty(aKey))
197  modifier = modifiers[aKey];
198 
199  else
200  {
201  var string = nil,
202  capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1),
203  isKey = nil;
204 
205  if ([theClass instancesRespondToSelector:string = sel_getUid("set" + capitalizedKey + ":")] ||
206  //FIXME: deprecated in Cocoa 10.3
207  [theClass instancesRespondToSelector:string = sel_getUid("_set" + capitalizedKey + ":")])
208  modifier = modifiers[aKey] = [0, string];
209 
210  else if (class_getInstanceVariable(theClass, string = "_" + aKey) ||
211  class_getInstanceVariable(theClass, string = "_" + (isKey = "is" + capitalizedKey)) ||
212  class_getInstanceVariable(theClass, string = aKey) ||
213  class_getInstanceVariable(theClass, string = isKey))
214  modifier = modifiers[aKey] = [1, string];
215 
216  else
217  modifier = modifiers[aKey] = [];
218  }
219 
220  switch (modifier[0])
221  {
222  case 0: return self.isa.objj_msgSend1(self, modifier[1], aValue);
223 
224  case 1: if ([theClass accessInstanceVariablesDirectly])
225  {
226  [self willChangeValueForKey:aKey];
227 
228  self[modifier[1]] = aValue;
229 
230  return [self didChangeValueForKey:aKey];
231  }
232  }
233 
234  return [self setValue:aValue forUndefinedKey:aKey];
235 
236 }
237 
238 - (void)setValuesForKeysWithDictionary:(CPDictionary)keyedValues
239 {
240  var value,
241  key,
242  keyEnumerator = [keyedValues keyEnumerator];
243 
244  while ((key = [keyEnumerator nextObject]) !== nil)
245  {
246  value = [keyedValues objectForKey: key];
247 
248  if (value === [CPNull null])
249  [self setValue: nil forKey: key];
250 
251  else
252  [self setValue: value forKey: key];
253  }
254 }
255 
256 - (void)setValue:(id)aValue forUndefinedKey:(CPString)aKey
257 {
258  [[CPException exceptionWithName:CPUndefinedKeyException
259  reason:[self _objectDescription] + " is not key value coding-compliant for the key " + aKey
260  userInfo:@{ CPTargetObjectUserInfoKey: self, CPUnknownUserInfoKey: aKey }] raise];
261 }
262 
263 - (CPString)_objectDescription
264 {
265  return "<" + [self className] + " 0x" + [CPString stringWithHash:[self UID]] + ">";
266 }
267 
268 @end
269 
271 
272 - (id)valueForKey:(CPString)aKey
273 {
274  if ([aKey hasPrefix:@"@"])
275  return [super valueForKey:aKey.substr(1)];
276 
277  return [self objectForKey:aKey];
278 }
279 
280 - (void)setValue:(id)aValue forKey:(CPString)aKey
281 {
282  if (aValue !== nil)
283  [self setObject:aValue forKey:aKey];
284 
285  else
286  [self removeObjectForKey:aKey];
287 }
288 
289 @end
290 
291 @implementation CPNull (CPKeyValueCoding)
292 
293 - (id)valueForKey:(CPString)aKey
294 {
295  return self;
296 }
297 
298 @end
299 
300 @implementation _CPKeyValueCodingArray : CPArray
301 {
302  id _target;
303 
304  SEL _countOfSelector;
305  SEL _objectInAtIndexSelector;
306  SEL _atIndexesSelector;
307 }
308 
309 - (id)initWithTarget:(id)aTarget key:(CPString)aKey
310 {
311  self = [super init];
312 
313  if (self)
314  {
315  var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1);
316 
317  _target = aTarget;
318 
319  _countOfSelector = CPSelectorFromString("countOf" + capitalizedKey);
320 
321  _objectInAtIndexSelector = CPSelectorFromString("objectIn" + capitalizedKey + "AtIndex:");
322 
323  if (![_target respondsToSelector:_objectInAtIndexSelector])
324  _objectInAtIndexSelector = nil;
325 
326  _atIndexesSelector = CPSelectorFromString(aKey + "AtIndexes:");
327 
328  if (![_target respondsToSelector:_atIndexesSelector])
329  _atIndexesSelector = nil;
330  }
331 
332  return self;
333 }
334 
335 - (CPUInteger)count
336 {
337  return _target == nil ? nil : _target.isa.objj_msgSend0(_target, _countOfSelector);
338 }
339 
340 - (id)objectAtIndex:(CPUInteger)anIndex
341 {
342  if (_objectInAtIndexSelector)
343  return _target == nil ? nil : _target.isa.objj_msgSend1(_target, _objectInAtIndexSelector, anIndex);
344 
345  return _target == nil ? nil : _target.isa.objj_msgSend1(_target, _atIndexesSelector, [CPIndexSet indexSetWithIndex:anIndex])[0];
346 }
347 
348 - (CPArray)objectsAtIndexes:(CPIndexSet)indexes
349 {
350  if (_atIndexesSelector)
351  return _target == nil ? nil : _target.isa.objj_msgSend1(_target, _atIndexesSelector, indexes);
352 
353  return [super objectsAtIndexes:indexes];
354 }
355 
356 - (Class)classForCoder
357 {
358  return [CPArray class];
359 }
360 
361 - (id)copy
362 {
363  // We do this to ensure we return a CPArray.
364  return [CPArray arrayWithArray:self];
365 }
366 
367 @end
368 
369 @implementation _CPKeyValueCodingSet : CPSet
370 {
371  id _target;
372 
373  SEL _countOfSelector;
374  SEL _enumeratorOfSelector;
375  SEL _memberOfSelector;
376 }
377 
378 // This allows things like setByAddingObject: to work (since they use [[self class] alloc] internally).
379 - (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
380 {
381  return [[CPSet alloc] initWithObjects:objects count:aCount];
382 }
383 
384 - (id)initWithTarget:(id)aTarget key:(CPString)aKey
385 {
386  self = [super initWithObjects:nil count:0];
387 
388  if (self)
389  {
390  var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1);
391 
392  _target = aTarget;
393 
394  _countOfSelector = CPSelectorFromString("countOf" + capitalizedKey);
395  _enumeratorOfSelector = CPSelectorFromString("enumeratorOf" + capitalizedKey);
396  _memberOfSelector = CPSelectorFromString("memberOf" + capitalizedKey + ":");
397  }
398 
399  return self;
400 }
401 
402 - (CPUInteger)count
403 {
404  return _target == nil ? nil : _target.isa.objj_msgSend0(_target, _countOfSelector);
405 }
406 
407 - (CPEnumerator)objectEnumerator
408 {
409  return _target == nil ? nil : _target.isa.objj_msgSend0(_target, _enumeratorOfSelector);
410 }
411 
412 - (id)member:(id)anObject
413 {
414  return _target == nil ? nil : _target.isa.objj_msgSend1(_target, _memberOfSelector, anObject);
415 }
416 
417 - (Class)classForCoder
418 {
419  return [CPSet class];
420 }
421 
422 - (id)copy
423 {
424  // We do this to ensure we return a CPSet.
425  return [CPSet setWithSet:self];
426 }
427 
428 @end
429 
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
var CPObjectAccessorsForClassKey
void willChangeValueForKey:(CPString aKey)
var CPObjectModifiersForClassKey
An object representation of nil.
Definition: CPNull.h:2
void setValue:forUndefinedKey:(id aValue, [forUndefinedKey] CPString aKey)
CPEnumerator keyEnumerator()
Definition: CPDictionary.j:429
A mutable key-value pair collection.
Definition: CPDictionary.h:2
A generic "value". Can be subclassed to hold specific data types.
Definition: CPValue.h:2
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
id valueForKey:(CPString aKey)
CPUndefinedKeyException
void didChangeValueForKey:(CPString aKey)
CPUnknownUserInfoKey
CPTargetObjectUserInfoKey
id valueForUndefinedKey:(CPString aKey)
CPNotFound
Definition: CPObjJRuntime.j:62
void setValue:forKey:(id aValue, [forKey] CPString aKey)
Class class()
Definition: CPObject.j:179
CPException exceptionWithName:reason:userInfo:(CPString aName, [reason] CPString aReason, [userInfo] CPDictionary aUserInfo)
Definition: CPException.j:94