API  1.0.0
CPKeyedUnarchiver.j
Go to the documentation of this file.
1 /*
2  * CPKeyedUnarchiver.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 CPInvalidUnarchiveOperationException = @"CPInvalidUnarchiveOperationException";
25 
26 var _CPKeyedUnarchiverCannotDecodeObjectOfClassNameOriginalClassesSelector = 1 << 0,
27  _CPKeyedUnarchiverDidDecodeObjectSelector = 1 << 1,
28  _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector = 1 << 2,
29  _CPKeyedUnarchiverWillFinishSelector = 1 << 3,
30  _CPKeyedUnarchiverDidFinishSelector = 1 << 4,
32 
33 var _CPKeyedArchiverNullString = "$null",
34 
35  _CPKeyedArchiverUIDKey = "CP$UID",
36 
37  _CPKeyedArchiverTopKey = "$top",
38  _CPKeyedArchiverObjectsKey = "$objects",
39  _CPKeyedArchiverArchiverKey = "$archiver",
40  _CPKeyedArchiverVersionKey = "$version",
41 
42  _CPKeyedArchiverClassNameKey = "$classname",
43  _CPKeyedArchiverClassesKey = "$classes",
44  _CPKeyedArchiverClassKey = "$class";
45 
46 var CPArrayClass = Nil,
52  CPDataClass = Nil,
53  _CPKeyedArchiverValueClass = Nil;
54 
100 @implementation CPKeyedUnarchiver : CPCoder
101 {
102  id _delegate;
103  unsigned _delegateSelectors;
104 
105  CPData _data;
106 
107  CPDictionary _replacementClasses;
108 
109  CPArray _objects;
110  CPDictionary _archive;
111 
112  CPDictionary _plistObject;
113  CPArray _plistObjects;
114 }
115 
116 /*
117  @ignore
118 */
119 + (void)initialize
120 {
121  if (self !== [CPKeyedUnarchiver class])
122  return;
123 
124  CPArrayClass = [CPArray class];
131  _CPKeyedArchiverValueClass = [_CPKeyedArchiverValue class];
132 }
133 
134 /*
135  Initializes the receiver to unarchive objects from the specified data object.
136  @param data the data stream from which to read objects
137  @return the initialized unarchiver
138 */
139 - (id)initForReadingWithData:(CPData)data
140 {
141  self = [super init];
142 
143  if (self)
144  {
145  _archive = [data plistObject];
146  _objects = [[CPNull null]];
147 
148  _plistObject = [_archive objectForKey:_CPKeyedArchiverTopKey];
149  _plistObjects = [_archive objectForKey:_CPKeyedArchiverObjectsKey];
150 
151  _replacementClasses = new CFMutableDictionary();
152  }
153 
154  return self;
155 }
156 
157 /*
158  Unarchives the object graph in the provided data object.
159  @param data the data from which to read the graph
160  @return the unarchived object
161 */
162 + (id)unarchiveObjectWithData:(CPData)aData
163 {
164  if (!aData)
165  {
166  CPLog.error("Null data passed to -[CPKeyedUnarchiver unarchiveObjectWithData:].");
167  return nil;
168  }
169 
170  var unarchiver = [[self alloc] initForReadingWithData:aData],
171  object = [unarchiver decodeObjectForKey:@"root"];
172 
173  [unarchiver finishDecoding];
174 
175  return object;
176 }
177 
178 /*
179  Not implemented
180 */
181 + (id)unarchiveObjectWithFile:(CPString)aFilePath
182 {
183 }
184 
185 /*
186  Not implemented
187 */
188 + (id)unarchiveObjectWithFile:(CPString)aFilePath asynchronously:(BOOL)aFlag
189 {
190 }
191 
192 /*
193  Returns \c YES if an object exists for \c aKey.
194  @param aKey the object's associated key
195 */
196 - (BOOL)containsValueForKey:(CPString)aKey
197 {
198  return _plistObject.valueForKey(aKey) != nil;
199 }
200 
201 /* @ignore */
202 - (CPDictionary)_decodeDictionaryOfObjectsForKey:(CPString)aKey
203 {
204  var object = _plistObject.valueForKey(aKey),
205  objectClass = (object != nil) && object.isa;
206 
207  if (objectClass === CPDictionaryClass || objectClass === CPMutableDictionaryClass)
208  {
209  var keys = object.keys(),
210  index = 0,
211  count = keys.length,
212  dictionary = new CFMutableDictionary();
213 
214  for (; index < count; ++index)
215  {
216  var key = keys[index];
217 
218  dictionary.setValueForKey(key, _CPKeyedUnarchiverDecodeObjectAtIndex(self, object.valueForKey(key).valueForKey(_CPKeyedArchiverUIDKey)));
219  }
220 
221  return dictionary;
222  }
223 
224  return nil;
225 }
226 
227 /*
228  Decodes a \c BOOL from the archive
229  @param aKey the \c BOOL's associated key
230  @return the decoded \c BOOL
231 */
232 - (BOOL)decodeBoolForKey:(CPString)aKey
233 {
234  return !![self decodeObjectForKey:aKey];
235 }
236 
237 /*
238  Decodes a \c float from the archive
239  @param aKey the \c float's associated key
240  @return the decoded \c float
241 */
242 - (float)decodeFloatForKey:(CPString)aKey
243 {
244  var f = [self decodeObjectForKey:aKey];
245 
246  return f === nil ? 0.0 : f;
247 }
248 
249 /*
250  Decodes a \c double from the archive.
251  @param aKey the \c double's associated key
252  @return the decoded \c double
253 */
254 - (double)decodeDoubleForKey:(CPString)aKey
255 {
256  var d = [self decodeObjectForKey:aKey];
257 
258  return d === nil ? 0.0 : d;
259 }
260 
261 /*
262  Decodes an \c int from the archive.
263  @param aKey the \c int's associated key
264  @return the decoded \c int
265 */
266 - (int)decodeIntForKey:(CPString)aKey
267 {
268  var i = [self decodeObjectForKey:aKey];
269 
270  return i === nil ? 0 : i;
271 }
272 
273 /*
274  Decodes a CGPoint from the archive.
275  @param aKey the point's associated key
276  @return the decoded point
277 */
278 - (CGPoint)decodePointForKey:(CPString)aKey
279 {
280  var object = [self decodeObjectForKey:aKey];
281 
282  if (object)
283  return CGPointFromString(object);
284  else
285  return CGPointMakeZero();
286 }
287 
288 /*
289  Decodes a CGRect from the archive.
290  @param aKey the rectangle's associated key
291  @return the decoded rectangle
292 */
293 - (CGRect)decodeRectForKey:(CPString)aKey
294 {
295  var object = [self decodeObjectForKey:aKey];
296 
297  if (object)
298  return CGRectFromString(object);
299  else
300  return CGRectMakeZero();
301 }
302 
303 /*
304  Decodes a CGSize from the archive.
305  @param aKey the size's associated key
306  @return the decoded size
307 */
308 - (CGSize)decodeSizeForKey:(CPString)aKey
309 {
310  var object = [self decodeObjectForKey:aKey];
311 
312  if (object)
313  return CGSizeFromString(object);
314  else
315  return CGSizeMakeZero();
316 }
317 
318 /*
319  Decodes an object from the archive.
320  @param aKey the object's associated key
321  @return the decoded object
322 */
323 - (id)decodeObjectForKey:(CPString)aKey
324 {
325  var object = _plistObject && _plistObject.valueForKey(aKey),
326  objectClass = (object != nil) && object.isa;
327 
328  if (objectClass === CPDictionaryClass || objectClass === CPMutableDictionaryClass)
329  return _CPKeyedUnarchiverDecodeObjectAtIndex(self, object.valueForKey(_CPKeyedArchiverUIDKey));
330 
331  else if (objectClass === CPNumberClass || objectClass === CPDataClass || objectClass === CPStringClass)
332  return object;
333 
334  else if (objectClass === _CPJavaScriptArray)
335  {
336  var index = 0,
337  count = object.length,
338  array = [];
339 
340  for (; index < count; ++index)
341  array[index] = _CPKeyedUnarchiverDecodeObjectAtIndex(self, object[index].valueForKey(_CPKeyedArchiverUIDKey));
342 
343  return array;
344  }
345 /* else
346  CPLog([object className] + " " + object + " " + aKey + " " + [_plistObject description]);*/
347 
348  return nil;
349 }
350 
351 /*
352  Decodes bytes from the archive.
353  @param aKey the object's associated key
354  @return array of bytes
355 */
356 - (id)decodeBytesForKey:(CPString)aKey
357 {
358  // We get the CPData wrapper, then extract the bytes array
359  var data = [self decodeObjectForKey:aKey];
360 
361  if (!data)
362  return nil;
363 
364  var objectClass = data.isa;
365 
366  if (objectClass === CPDataClass)
367  return data.bytes();
368 
369  return nil;
370 }
371 
372 /*
373  Notifies the delegates that decoding has finished.
374 */
375 - (void)finishDecoding
376 {
377  if (_delegateSelectors & _CPKeyedUnarchiverWillFinishSelector)
378  [_delegate unarchiverWillFinish:self];
379 
380  if (_delegateSelectors & _CPKeyedUnarchiverDidFinishSelector)
381  [_delegate unarchiverDidFinish:self];
382 }
383 
384 /*
385  Returns the keyed unarchiver's delegate
386 */
387 - (id)delegate
388 {
389  return _delegate;
390 }
391 
392 /*
393  Sets the unarchiver's delegate
394  @param the new delegate
395 */
396 - (void)setDelegate:(id)aDelegate
397 {
398  _delegate = aDelegate;
399 
400  if ([_delegate respondsToSelector:@selector(unarchiver:cannotDecodeObjectOfClassName:originalClasses:)])
401  _delegateSelectors |= _CPKeyedUnarchiverCannotDecodeObjectOfClassNameOriginalClassesSelector;
402 
403  if ([_delegate respondsToSelector:@selector(unarchiver:didDecodeObject:)])
404  _delegateSelectors |= _CPKeyedUnarchiverDidDecodeObjectSelector;
405 
406  if ([_delegate respondsToSelector:@selector(unarchiver:willReplaceObject:withObject:)])
407  _delegateSelectors |= _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector;
408 
409  if ([_delegate respondsToSelector:@selector(unarchiverWillFinish:)])
410  _delegateSelectors |= _CPKeyedUnarchiverWillFinishSelector;
411 
412  if ([_delegate respondsToSelector:@selector(unarchiverDidFinish:)])
413  _delegateSelectors |= _CPKeyedUnarchiverDidFinishSelector;
414 
415  if ([_delegate respondsToSelector:@selector(unarchiver:cannotDecodeObjectOfClassName:originalClasses:)])
417 }
418 
419 - (void)setClass:(Class)aClass forClassName:(CPString)aClassName
420 {
421  _replacementClasses.setValueForKey(aClassName, aClass);
422 }
423 
424 - (Class)classForClassName:(CPString)aClassName
425 {
426  return _replacementClasses.valueForKey(aClassName);
427 }
428 
429 - (BOOL)allowsKeyedCoding
430 {
431  return YES;
432 }
433 
434 @end
435 
436 var _CPKeyedUnarchiverDecodeObjectAtIndex = function(self, anIndex)
437 {
438  var object = self._objects[anIndex];
439 
440  if (object)
441  {
442  if (object === self._objects[0])
443  return nil;
444  // Don't return immediately here. The _CPKeyedArchiverValueClass unwrapper code
445  // hasn't executed yet.
446  }
447  else
448  {
449  var plistObject = self._plistObjects[anIndex],
450  plistObjectClass = plistObject.isa;
451 
452  if (plistObjectClass === CPDictionaryClass || plistObjectClass === CPMutableDictionaryClass)
453  {
454  var plistClass = self._plistObjects[plistObject.valueForKey(_CPKeyedArchiverClassKey).valueForKey(_CPKeyedArchiverUIDKey)],
455  className = plistClass.valueForKey(_CPKeyedArchiverClassNameKey),
456  classes = plistClass.valueForKey(_CPKeyedArchiverClassesKey),
457  theClass = [self classForClassName:className];
458 
459  if (!theClass)
460  theClass = CPClassFromString(className);
461 
462  if (!theClass &&
464  {
465  theClass = [self._delegate unarchiver:self cannotDecodeObjectOfClassName:className originalClasses:classes];
466  }
467 
468  if (!theClass)
469  [CPException raise:CPInvalidUnarchiveOperationException format:@"-[CPKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (%@)", className];
470 
471  var savedPlistObject = self._plistObject;
472 
473  self._plistObject = plistObject;
474 
475  // Should we only call this on _CPCibClassSwapper? (currently the only class that makes use of this).
476  object = [theClass allocWithCoder:self];
477 
478  // It is important to do this before calling initWithCoder so that decoding can be self referential (something = self).
479  self._objects[anIndex] = object;
480 
481  var processedObject = [object initWithCoder:self];
482 
483  self._plistObject = savedPlistObject;
484 
485  if (processedObject !== object)
486  {
487  if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector)
488  [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject];
489 
490  object = processedObject;
491  self._objects[anIndex] = processedObject;
492  }
493 
494  processedObject = [object awakeAfterUsingCoder:self];
495 
496  if (processedObject !== object)
497  {
498  if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector)
499  [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject];
500 
501  object = processedObject;
502  self._objects[anIndex] = processedObject;
503  }
504 
505  if (self._delegate)
506  {
507  if (self._delegateSelectors & _CPKeyedUnarchiverDidDecodeObjectSelector)
508  processedObject = [self._delegate unarchiver:self didDecodeObject:object];
509 
510  if (processedObject && processedObject != object)
511  {
512  if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector)
513  [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject];
514 
515  object = processedObject;
516  self._objects[anIndex] = processedObject;
517  }
518  }
519  }
520  else
521  {
522  self._objects[anIndex] = object = plistObject;
523 
524  if ([object class] === CPStringClass)
525  {
526  if (object === _CPKeyedArchiverNullString)
527  {
528  self._objects[anIndex] = self._objects[0];
529 
530  return nil;
531  }
532  else
533  self._objects[anIndex] = object = plistObject;
534  }
535  }
536  }
537 
538  // If this object is a member of _CPKeyedArchiverValue, then we know
539  // that it is a wrapper for a primitive JavaScript object.
540  if ((object != nil) && (object.isa === _CPKeyedArchiverValueClass))
541  object = [object JSObject];
542 
543  return object;
544 };
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
var CPNumberClass
An object representation of nil.
Definition: CPNull.h:2
void raise:format:(CPString aName, [format] CPString aFormat, [,] ...)
Definition: CPException.j:77
var CPDictionaryClass
CFData prototype isa
Definition: CPData.j:214
CPArray bytes()
Definition: CPData.j:136
id delegate()
Definition: CALayer.j:965
A Cappuccino wrapper for any data type.
Definition: CPData.h:2
id initForReadingWithData:(CPData data)
Unarchives objects created using CPKeyedArchiver.
A mutable key-value pair collection.
Definition: CPDictionary.h:2
id plistObject()
Definition: CPData.j:126
CPInvalidUnarchiveOperationException
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
id decodeObjectForKey:(CPString aKey)
var CPDataClass
var CPArrayClass
var CPKeyedUnarchiverDelegate_unarchiver_cannotDecodeObjectOfClassName_originalClasses_
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
id init()
Definition: CPObject.j:145
var CPMutableArrayClass
var CPStringClass
Class class()
Definition: CPObject.j:179
var CPMutableDictionaryClass
A bridged object to native Javascript numbers.
Definition: CPNumber.h:2
function CPClassFromString(aClassName)
Definition: CPObjJRuntime.j:33
id alloc()
Definition: CPObject.j:130