/* * CPKeyedUnarchiver.j * Foundation * * Created by Francisco Tolmasky. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ import "CPNull.j" import "CPCoder.j" var _CPKeyedUnarchiverCannotDecodeObjectOfClassNameOriginalClassesSelector = 1, _CPKeyedUnarchiverDidDecodeObjectSelector = 1 << 1, _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector = 1 << 2, _CPKeyedUnarchiverWillFinishSelector = 1 << 3, _CPKeyedUnarchiverDidFinishSelector = 1 << 4; var _CPKeyedArchiverNullString = "$null" _CPKeyedArchiverUIDKey = "CP$UID", _CPKeyedArchiverTopKey = "$top", _CPKeyedArchiverObjectsKey = "$objects", _CPKeyedArchiverArchiverKey = "$archiver", _CPKeyedArchiverVersionKey = "$version", _CPKeyedArchiverClassNameKey = "$classname", _CPKeyedArchiverClassesKey = "$classes", _CPKeyedArchiverClassKey = "$class"; var _CPKeyedUnarchiverArrayClass = Nil, _CPKeyedUnarchiverStringClass = Nil, _CPKeyedUnarchiverDictionaryClass = Nil, _CPKeyedUnarchiverArchiverValueClass = Nil; /* CPKeyedUnarchiver is used for creating objects out of coded files or CPData objects that were created by CPKeyedArchiver. More specifically, this class unarchives objects from a data stream or file and brings them back into memory for programmatic usage. @delegate -(Class)unarchiver:(CPKeyedUnarchiver)unarchiver cannotDecodeObjectOfClassName:(CPString)name originalClasses:(CPArray)classNames; Called when the specified class is not available during decoding. The delegate may load the class, or return a substitute class to use instead. @param unarchiver the unarchiver performing the decode @param name the name of the class that can't be found @param an array of class names describing the encoded object's class hierarchy. The first index is the encoded class name, and each superclass is after that. @return the Class to use instead or nil to abort the unarchiving operation @delegate -(id)unarchiver:(CPKeyedUnarchiver)unarchiver didDecodeObject:(id)object; Called when the unarchiver decodes an object. @param unarchiver the unarchiver doing the decoding @param object the decoded objec @return a substitute to use for the decoded object. This can be the same object argument provide, another object or nil. @delegate -(void)unarchiver:(CPKeyedUnarchiver)unarchiver willReplaceObject:(id)object withObject:(id)newObject; Called when a decoded object has been substituted with another. (for example, from unarchiver:didDecodeObject:. @param unarchiver the unarchiver that decoded the object @param object the original decoded object @param newObject the replacement object @delegate -(void)unarchiverWillFinish:(CPKeyedUnarchiver)unarchiver; Called when the unarchiver is about to finish decoding. @param unarchiver the unarchiver that's about to finish @delegate -(void)unarchiverDidFinish:(CPKeyedUnarchiver)unarchiver; Called when the unarchiver has finished decoding. @param unarchiver the unarchiver that finished decoding */ @implementation CPKeyedUnarchiver : CPCoder { id _delegate; unsigned _delegateSelectors; CPData _data; // FIXME: We need to support this! CPDictionary _replacementClassNames; CPDictionary _objects; CPDictionary _archive; CPDictionary _plistObject; CPArray _plistObjects; } /* @ignore */ + (void)initialize { if (self != [CPKeyedUnarchiver class]) return; _CPKeyedUnarchiverArrayClass = [CPArray class]; _CPKeyedUnarchiverStringClass = [CPString class]; _CPKeyedUnarchiverDictionaryClass = [CPDictionary class]; _CPKeyedUnarchiverArchiverValueClass = [_CPKeyedArchiverValue class]; } /* Initializes the receiver to unarchive objects from the specified data object. @param data the data stream from which to read objects @return the initialized unarchiver */ - (id)initForReadingWithData:(CPData)data { self = [super init]; if (self) { _archive = [data plistObject]; _objects = [CPArray arrayWithObject:[CPNull null]]; _plistObject = [_archive objectForKey:_CPKeyedArchiverTopKey]; _plistObjects = [_archive objectForKey:_CPKeyedArchiverObjectsKey]; } return self; } /* Unarchives the object graph in the provided data object. @param data the data from which to read the graph @return the unarchived object */ + (id)unarchiveObjectWithData:(CPData)data { var unarchiver = [[self alloc] initForReadingWithData:data], object = [unarchiver decodeObjectForKey:@"root"]; [unarchiver finishDecoding]; return object; } /* Not implemented */ + (id)unarchiveObjectWithFile:(CPString)aFilePath { } /* Not implemented */ + (id)unarchiveObjectWithFile:(CPString)aFilePath asynchronously:(BOOL)aFlag { } /* Returns YES if an object exists for aKey. @param aKey the object's associated key */ - (BOOL)containsValueForKey:(CPString)aKey { return [_plistObject objectForKey:aKey] != nil; } /* @ignore */ - (id)_decodeArrayOfObjectsForKey:(CPString)aKey { var object = [_plistObject objectForKey:aKey]; if ([object isKindOfClass:_CPKeyedUnarchiverArrayClass]) { var index = 0, count = object.length, array = []; for (; index < count; ++index) array[index] = _CPKeyedUnarchiverDecodeObjectAtIndex(self, [object[index] objectForKey:_CPKeyedArchiverUIDKey]); return array; } return nil; } /* @ignore */ - (void)_decodeDictionaryOfObjectsForKey:(CPString)aKey { var object = [_plistObject objectForKey:aKey]; if ([object isKindOfClass:_CPKeyedUnarchiverDictionaryClass]) { var key, keys = [object keyEnumerator], dictionary = [CPDictionary dictionary]; while (key = [keys nextObject]) [dictionary setObject:_CPKeyedUnarchiverDecodeObjectAtIndex(self, [[object objectForKey:key] objectForKey:_CPKeyedArchiverUIDKey]) forKey:key]; return dictionary; } return nil; } /* Decodes a BOOL from the archive @param aKey the BOOL's associated key @return the decoded BOOL */ - (BOOL)decodeBoolForKey:(CPString)aKey { return [self decodeObjectForKey:aKey]; } /* Decodes a float from the archive @param aKey the float's associated key @return the decoded float */ - (float)decodeFloatForKey:(CPString)aKey { return [self decodeObjectForKey:aKey]; } /* Decodes a double from the archive. @param aKey the double's associated key @return the decoded double */ - (double)decodeDoubleForKey:(CPString)aKey { return [self decodeObjectForKey:aKey]; } /* Decodes an int from the archive. @param aKey the int's associated key @return the decoded int */ - (int)decodeIntForKey:(CPString)aKey { return [self decodeObjectForKey:aKey]; } /* Decodes a CGPoint from the archive. @param aKey the point's associated key @return the decoded point */ - (CGPoint)decodePointForKey:(CPString)aKey { var object = [self decodeObjectForKey:aKey]; if(object) return CPPointFromString(object); else return CPPointMake(0.0, 0.0); } /* Decodes a CGRect from the archive. @param aKey the rectangle's associated key @return the decoded rectangle */ - (CGRect)decodeRectForKey:(CPString)aKey { var object = [self decodeObjectForKey:aKey]; if(object) return CPRectFromString(object); else return CPRectMakeZero(); } /* Decodes a CGSize from the archive. @param aKey the size's associated key @return the decoded size */ - (CGSize)decodeSizeForKey:(CPString)aKey { var object = [self decodeObjectForKey:aKey]; if(object) return CPSizeFromString(object); else return CPSizeMake(0.0, 0.0); } /* Decodes an object from the archive. @param aKey the object's associated key @return the decoded object */ - (id)decodeObjectForKey:(CPString)aKey { var object = [_plistObject objectForKey:aKey]; if ([object isKindOfClass:_CPKeyedUnarchiverDictionaryClass]) return _CPKeyedUnarchiverDecodeObjectAtIndex(self, [object objectForKey:_CPKeyedArchiverUIDKey]); else if ([object isKindOfClass:[CPNumber class]]) return object; /* else alert([object className] + " " + object + " " + aKey + " " + [_plistObject description]);*/ return nil; } /* Notifies the delegates that decoding has finished. */ - (void)finishDecoding { if (_delegateSelectors & _CPKeyedUnarchiverWillFinishSelector) [_delegate unarchiverWillFinish:self]; if (_delegateSelectors & _CPKeyedUnarchiverDidFinishSelector) [_delegate unarchiverDidFinish:self]; } /* Returns the keyed unarchiver's delegate */ - (id)delegate { return _delegate; } /* Sets the unarchiver's delegate @param the new delegate */ - (void)setDelegate:(id)aDelegate { _delegate = aDelegate; if ([_delegate respondsToSelector:@selector(unarchiver:cannotDecodeObjectOfClassName:originalClasses:)]) _delegateSelectors |= _CPKeyedUnarchiverCannotDecodeObjectOfClassNameOriginalClassesSelector; if ([_delegate respondsToSelector:@selector(unarchiver:didDecodeObject:)]) _delegateSelectors |= _CPKeyedUnarchiverDidDecodeObjectSelector; if ([_delegate respondsToSelector:@selector(unarchiver:willReplaceObject:withObject:)]) _delegateSelectors |= _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector; if ([_delegate respondsToSelector:@selector(unarchiverWillFinish:)]) _delegateSelectors |= _CPKeyedUnarchiverWilFinishSelector; if ([_delegate respondsToSelector:@selector(unarchiverDidFinish:)]) _delegateSelectors |= _CPKeyedUnarchiverDidFinishSelector; } - (BOOL)allowsKeyedCoding { return YES; } @end var _CPKeyedUnarchiverDecodeObjectAtIndex = function(self, anIndex) { var object = self._objects[anIndex]; if (object) if (object == self._objects[0]) return nil; else return object; var object, plistObject = self._plistObjects[anIndex]; if ([plistObject isKindOfClass:_CPKeyedUnarchiverDictionaryClass]) { var plistClass = self._plistObjects[[[plistObject objectForKey:_CPKeyedArchiverClassKey] objectForKey:_CPKeyedArchiverUIDKey]], className = [plistClass objectForKey:_CPKeyedArchiverClassNameKey], classes = [plistClass objectForKey:_CPKeyedArchiverClassesKey], theClass = CPClassFromString(className); object = [theClass alloc]; // It is important to do this before calling initWithCoder so that decoding can be self referential (something = self). self._objects[anIndex] = object; var savedPlistObject = self._plistObject; self._plistObject = plistObject; var processedObject = [object initWithCoder:self]; self._plistObject = savedPlistObject; if (processedObject != object) { if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; object = processedObject; self._objects[anIndex] = processedObject; } processedObject = [object awakeAfterUsingCoder:self]; if (processedObject != object) { if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; object = processedObject; self._objects[anIndex] = processedObject; } if (self._delegate) { if (self._delegateSelectors & _CPKeyedUnarchiverDidDecodeObjectSelector) processedObject = [self._delegate unarchiver:self didDecodeObject:object]; if (processedObject != object) { if (self._delegateSelectors & _CPKeyedUnarchiverWillReplaceObjectWithObjectSelector) [self._delegate unarchiver:self willReplaceObject:object withObject:processedObject]; object = processedObject; self._objects[anIndex] = processedObject; } } } else { self._objects[anIndex] = object = plistObject; if ([object class] == _CPKeyedUnarchiverStringClass) { if (object == _CPKeyedArchiverNullString) { self._objects[anIndex] = self._objects[0]; return nil; } else self._objects[anIndex] = object = plistObject; } } // If this object is a member of _CPKeyedArchiverValue, then we know // that it is a wrapper for a primitive JavaScript object. if ([object isMemberOfClass:_CPKeyedUnarchiverArchiverValueClass]) object = [object JSObject]; return object; }