API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
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: return objj_msgSend(self, accessor[1]);
104  // FIXME: We shouldn't be creating a new one every time.
105  case 1: return [[_CPKeyValueCodingArray alloc] initWithTarget:self key:aKey];
106  // FIXME: We shouldn't be creating a new one every time.
107  case 2: return [[_CPKeyValueCodingSet alloc] initWithTarget:self key:aKey];
108  case 3: if ([theClass accessInstanceVariablesDirectly])
109  return self[accessor[1]];
110  }
111 
112  return [self valueForUndefinedKey:aKey];
113 }
114 
115 - (id)valueForKeyPath:(CPString)aKeyPath
116 {
117  var firstDotIndex = aKeyPath.indexOf(".");
118 
119  if (firstDotIndex === CPNotFound)
120  return [self valueForKey:aKeyPath];
121 
122  var firstKeyComponent = aKeyPath.substring(0, firstDotIndex),
123  remainingKeyPath = aKeyPath.substring(firstDotIndex + 1),
124  value = [self valueForKey:firstKeyComponent];
125 
126  return [value valueForKeyPath:remainingKeyPath];
127 }
128 
129 - (CPDictionary)dictionaryWithValuesForKeys:(CPArray)keys
130 {
131  var index = 0,
132  count = keys.length,
133  dictionary = [CPDictionary dictionary];
134 
135  for (; index < count; ++index)
136  {
137  var key = keys[index],
138  value = [self valueForKey:key];
139 
140  if (value === nil)
141  [dictionary setObject:[CPNull null] forKey:key];
142 
143  else
144  [dictionary setObject:value forKey:key];
145  }
146 
147  return dictionary;
148 }
149 
150 - (id)valueForUndefinedKey:(CPString)aKey
151 {
152  [[CPException exceptionWithName:CPUndefinedKeyException
153  reason:[self _objectDescription] + " is not key value coding-compliant for the key " + aKey
154  userInfo:[CPDictionary dictionaryWithObjects:[self, aKey] forKeys:[CPTargetObjectUserInfoKey, CPUnknownUserInfoKey]]] raise];
155 }
156 
157 - (void)setValue:(id)aValue forKeyPath:(CPString)aKeyPath
158 {
159  if (!aKeyPath)
160  aKeyPath = @"self";
161 
162  var firstDotIndex = aKeyPath.indexOf(".");
163 
164  if (firstDotIndex === CPNotFound)
165  return [self setValue:aValue forKey:aKeyPath];
166 
167  var firstKeyComponent = aKeyPath.substring(0, firstDotIndex),
168  remainingKeyPath = aKeyPath.substring(firstDotIndex + 1),
169  value = [self valueForKey:firstKeyComponent];
170 
171  return [value setValue:aValue forKeyPath:remainingKeyPath];
172 }
173 
174 - (void)setValue:(id)aValue forKey:(CPString)aKey
175 {
176  var theClass = [self class],
177  modifier = nil,
178  modifiers = theClass[CPObjectModifiersForClassKey];
179 
180  if (!modifiers)
181  modifiers = theClass[CPObjectModifiersForClassKey] = { };
182 
183  if (modifiers.hasOwnProperty(aKey))
184  modifier = modifiers[aKey];
185 
186  else
187  {
188  var string = nil,
189  capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1),
190  isKey = nil;
191 
192  if ([theClass instancesRespondToSelector:string = sel_getUid("set" + capitalizedKey + ":")] ||
193  //FIXME: deprecated in Cocoa 10.3
194  [theClass instancesRespondToSelector:string = sel_getUid("_set" + capitalizedKey + ":")])
195  modifier = modifiers[aKey] = [0, string];
196 
197  else if (class_getInstanceVariable(theClass, string = "_" + aKey) ||
198  class_getInstanceVariable(theClass, string = "_" + (isKey = "is" + capitalizedKey)) ||
199  class_getInstanceVariable(theClass, string = aKey) ||
200  class_getInstanceVariable(theClass, string = isKey))
201  modifier = modifiers[aKey] = [1, string];
202 
203  else
204  modifier = modifiers[aKey] = [];
205  }
206 
207  switch (modifier[0])
208  {
209  case 0: return objj_msgSend(self, modifier[1], aValue);
210 
211  case 1: if ([theClass accessInstanceVariablesDirectly])
212  {
213  [self willChangeValueForKey:aKey];
214 
215  self[modifier[1]] = aValue;
216 
217  return [self didChangeValueForKey:aKey];
218  }
219  }
220 
221  return [self setValue:aValue forUndefinedKey:aKey];
222 
223 }
224 
225 - (void)setValuesForKeysWithDictionary:(CPDictionary)keyedValues
226 {
227  var value,
228  key,
229  keyEnumerator = [keyedValues keyEnumerator];
230 
231  while ((key = [keyEnumerator nextObject]) !== nil)
232  {
233  value = [keyedValues objectForKey: key];
234 
235  if (value === [CPNull null])
236  [self setValue: nil forKey: key];
237 
238  else
239  [self setValue: value forKey: key];
240  }
241 }
242 
243 - (void)setValue:(id)aValue forUndefinedKey:(CPString)aKey
244 {
245  [[CPException exceptionWithName:CPUndefinedKeyException
246  reason:[self _objectDescription] + " is not key value coding-compliant for the key " + aKey
247  userInfo:[CPDictionary dictionaryWithObjects:[self, aKey] forKeys:[CPTargetObjectUserInfoKey, CPUnknownUserInfoKey]]] raise];
248 }
249 
250 - (CPString)_objectDescription
251 {
252  return "<" + [self className] + " 0x" + [CPString stringWithHash:[self UID]] + ">";
253 }
254 
255 @end
256 
258 
259 - (id)valueForKey:(CPString)aKey
260 {
261  if ([aKey hasPrefix:@"@"])
262  return [super valueForKey:aKey.substr(1)];
263 
264  return [self objectForKey:aKey];
265 }
266 
267 - (void)setValue:(id)aValue forKey:(CPString)aKey
268 {
269  if (aValue !== nil)
270  [self setObject:aValue forKey:aKey];
271 
272  else
273  [self removeObjectForKey:aKey];
274 }
275 
276 @end
277 
278 @implementation CPNull (CPKeyValueCoding)
279 
280 - (id)valueForKey:(CPString)aKey
281 {
282  return self;
283 }
284 
285 @end
286 
287 @implementation _CPKeyValueCodingArray : CPArray
288 {
289  id _target;
290 
291  SEL _countOfSelector;
292  SEL _objectInAtIndexSelector;
293  SEL _atIndexesSelector;
294 }
295 
296 - (id)initWithTarget:(id)aTarget key:(CPString)aKey
297 {
298  self = [super init];
299 
300  if (self)
301  {
302  var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1);
303 
304  _target = aTarget;
305 
306  _countOfSelector = CPSelectorFromString("countOf" + capitalizedKey);
307 
308  _objectInAtIndexSelector = CPSelectorFromString("objectIn" + capitalizedKey + "AtIndex:");
309 
310  if (![_target respondsToSelector:_objectInAtIndexSelector])
311  _objectInAtIndexSelector = nil;
312 
313  _atIndexesSelector = CPSelectorFromString(aKey + "AtIndexes:");
314 
315  if (![_target respondsToSelector:_atIndexesSelector])
316  _atIndexesSelector = nil;
317  }
318 
319  return self;
320 }
321 
322 - (CPUInteger)count
323 {
324  return objj_msgSend(_target, _countOfSelector);
325 }
326 
327 - (id)objectAtIndex:(CPUInteger)anIndex
328 {
329  if (_objectInAtIndexSelector)
330  return objj_msgSend(_target, _objectInAtIndexSelector, anIndex);
331 
332  return objj_msgSend(_target, _atIndexesSelector, [CPIndexSet indexSetWithIndex:anIndex])[0];
333 }
334 
335 - (CPArray)objectsAtIndexes:(CPIndexSet)indexes
336 {
337  if (_atIndexesSelector)
338  return objj_msgSend(_target, _atIndexesSelector, indexes);
339 
340  return [super objectsAtIndexes:indexes];
341 }
342 
343 - (Class)classForCoder
344 {
345  return [CPArray class];
346 }
347 
348 - (id)copy
349 {
350  // We do this to ensure we return a CPArray.
351  return [CPArray arrayWithArray:self];
352 }
353 
354 @end
355 
356 @implementation _CPKeyValueCodingSet : CPSet
357 {
358  id _target;
359 
360  SEL _countOfSelector;
361  SEL _enumeratorOfSelector;
362  SEL _memberOfSelector;
363 }
364 
365 // This allows things like setByAddingObject: to work (since they use [[self class] alloc] internally).
366 - (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
367 {
368  return [[CPSet alloc] initWithObjects:objects count:aCount];
369 }
370 
371 - (id)initWithTarget:(id)aTarget key:(CPString)aKey
372 {
373  self = [super initWithObjects:nil count:0];
374 
375  if (self)
376  {
377  var capitalizedKey = aKey.charAt(0).toUpperCase() + aKey.substr(1);
378 
379  _target = aTarget;
380 
381  _countOfSelector = CPSelectorFromString("countOf" + capitalizedKey);
382  _enumeratorOfSelector = CPSelectorFromString("enumeratorOf" + capitalizedKey);
383  _memberOfSelector = CPSelectorFromString("memberOf" + capitalizedKey + ":");
384  }
385 
386  return self;
387 }
388 
389 - (CPUInteger)count
390 {
391  return objj_msgSend(_target, _countOfSelector);
392 }
393 
394 - (CPEnumerator)objectEnumerator
395 {
396  return objj_msgSend(_target, _enumeratorOfSelector);
397 }
398 
399 - (id)member:(id)anObject
400 {
401  return objj_msgSend(_target, _memberOfSelector, anObject);
402 }
403 
404 - (Class)classForCoder
405 {
406  return [CPSet class];
407 }
408 
409 - (id)copy
410 {
411  // We do this to ensure we return a CPSet.
412  return [CPSet setWithSet:self];
413 }
414 
415 @end
416