Files
cappuccino/Foundation/CPKeyedArchiver.j
T

603 lines
19 KiB
Plaintext

/*
* CPKeyedArchiver.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 "CPArray.j"
@import "CPCoder.j"
@import "CPData.j"
@import "CPDictionary.j"
@import "CPNumber.j"
@import "CPString.j"
@import "CPValue.j"
var CPArchiverReplacementClassNames = nil;
var _CPKeyedArchiverDidEncodeObjectSelector = 1,
_CPKeyedArchiverWillEncodeObjectSelector = 2,
_CPKeyedArchiverWillReplaceObjectWithObjectSelector = 4,
_CPKeyedArchiverDidFinishSelector = 8,
_CPKeyedArchiverWillFinishSelector = 16;
var _CPKeyedArchiverNullString = "$null",
_CPKeyedArchiverNullReference = nil,
_CPKeyedArchiverUIDKey = "CP$UID",
_CPKeyedArchiverTopKey = "$top",
_CPKeyedArchiverObjectsKey = "$objects",
_CPKeyedArchiverArchiverKey = "$archiver",
_CPKeyedArchiverVersionKey = "$version",
_CPKeyedArchiverClassNameKey = "$classname",
_CPKeyedArchiverClassesKey = "$classes",
_CPKeyedArchiverClassKey = "$class";
var _CPKeyedArchiverStringClass = Nil,
_CPKeyedArchiverNumberClass = Nil;
/* @ignore */
@implementation _CPKeyedArchiverValue : CPValue
{
}
@end
/*!
@class CPKeyedArchiver
@ingroup foundation
@brief Implements keyed archiving of object graphs (e.g. for storing data).
Implements keyed archiving of object graphs. Archiving means to
write data out in a format that be read in again later, or possibly
stored in a file. To read the data back in, use a
\c CPKeyedUnarchiver.
@par Delegate Methods
@delegate -(void)archiverWillFinish:(CPKeyedArchiver)archiver;
Called when the encoding is about to finish.
@param archiver the archiver that's about to finish
@delegate -(void)archiver:(CPKeyedArchiver)archiver didEncodeObject:(id)object;
Called when an object is encoded into the archiver.
@param archiver the archiver that encoded the object
@param object the object that was encoded
@delegate -(void)archiverDidFinish:(CPKeyedArchiver)archiver;
Called when the archiver finishes encoding.
@param archiver the arhiver that finished encoding
@delegate -(id)archiver:(CPKeyedArchiver)archiver willEncodeObject:(id)object;
Called when an object is about to be encoded. Allows the delegate to replace
the object that gets encoded with a substitute or \c nil.
@param archiver the archiver encoding the object
@param object the candidate object for encoding
@return the object to encode
@delegate -(void)archiver:(CPKeyedArchiver)archiver willReplaceObject:(id)object withObject:(id)newObject;
Called when an object is being replaced in the archiver.
@param archiver the archiver encoding the object
@param object the object to be replaced
@param newObject the replacement object
*/
@implementation CPKeyedArchiver : CPCoder
{
id _delegate;
unsigned _delegateSelectors;
CPData _data;
CPArray _objects;
CPDictionary _UIDs;
CPDictionary _conditionalUIDs;
CPDictionary _replacementObjects;
CPDictionary _replacementClassNames;
id _plistObject;
CPMutableArray _plistObjects;
CPPropertyListFormat _outputFormat;
}
/*
@ignore
*/
+ (void)initialize
{
if (self != [CPKeyedArchiver class])
return;
_CPKeyedArchiverStringClass = [CPString class];
_CPKeyedArchiverNumberClass = [CPNumber class];
_CPKeyedArchiverNullReference = [CPDictionary dictionaryWithObject:0 forKey:_CPKeyedArchiverUIDKey];
}
+ (BOOL)allowsKeyedCoding
{
return YES;
}
/*!
Archives the specified object.
@param anObject the object to archive
@return the archived object
*/
+ (CPData)archivedDataWithRootObject:(id)anObject
{
var data = [CPData dataWithPlistObject:nil],
archiver = [[self alloc] initForWritingWithMutableData:data];
[archiver encodeObject:anObject forKey:@"root"];
[archiver finishEncoding];
return data;
}
// Initializing an NSKeyedArchiver object
/*!
Initializes the keyed archiver with the specified \c CPMutableData for writing.
@param data the object to archive to
@return the initialized keyed archiver
*/
- (id)initForWritingWithMutableData:(CPMutableData)data
{
self = [super init];
if (self)
{
_data = data;
_objects = [];
_UIDs = [CPDictionary dictionary];
_conditionalUIDs = [CPDictionary dictionary];
_replacementObjects = [CPDictionary dictionary];
_data = data;
_plistObject = [CPDictionary dictionary];
_plistObjects = [CPArray arrayWithObject:_CPKeyedArchiverNullString];
}
return self;
}
// Archiving Data
/*!
Finishes up writing any left over data, and notifies delegates.
After calling this method, the archiver can not encode anymore objects.
*/
- (void)finishEncoding
{
if (_delegate && _delegateSelectors & _CPKeyedArchiverWillFinishSelector)
[_delegate archiverWillFinish:self];
var i = 0,
topObject = _plistObject,
classes = [];
for (; i < _objects.length; ++i)
{
var object = _objects[i],
theClass = [object classForKeyedArchiver];
// Do whatever with the class, yo.
// We call willEncodeObject previously.
_plistObject = _plistObjects[[_UIDs objectForKey:[object UID]]];
[object encodeWithCoder:self];
if (_delegate && _delegateSelectors & _CPKeyedArchiverDidEncodeObjectSelector)
[_delegate archiver:self didEncodeObject:object];
}
_plistObject = [CPDictionary dictionary];
[_plistObject setObject:topObject forKey:_CPKeyedArchiverTopKey];
[_plistObject setObject:_plistObjects forKey:_CPKeyedArchiverObjectsKey];
[_plistObject setObject:[self className] forKey:_CPKeyedArchiverArchiverKey];
[_plistObject setObject:@"100000" forKey:_CPKeyedArchiverVersionKey];
[_data setPlistObject:_plistObject];
if (_delegate && _delegateSelectors & _CPKeyedArchiverDidFinishSelector)
[_delegate archiverDidFinish:self];
}
/*!
Returns the property list format used to archive objects.
*/
- (CPPropertyListFormat)outputFormat
{
return _outputFormat;
}
/*!
Sets the property list format the archiver should use to archive objects.
@param aPropertyListFormat the format to use
*/
- (void)setOutputFormat:(CPPropertyListFormat)aPropertyListFormat
{
_outputFormat = aPropertyListFormat;
}
/*!
Encodes a \c BOOL value
@param aBool the \c BOOL value
@param aKey the key to associate with the \c BOOL
*/
- (void)encodeBool:(BOOL)aBOOL forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, aBOOL, NO) forKey:aKey];
}
/*!
Encodes a \c double value
@param aDouble the \c double value
@param aKey the key to associate with the \c double
*/
- (void)encodeDouble:(double)aDouble forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, aDouble, NO) forKey:aKey];
}
/*!
Encodes a \c float value
@param aFloat the \c float value
@param aKey the key to associate with the \c float
*/
- (void)encodeFloat:(float)aFloat forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, aFloat, NO) forKey:aKey];
}
/*!
Encodes a \c int value
@param anInt the \c int value
@param aKey the key to associate with the \c int
*/
- (void)encodeInt:(float)anInt forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, anInt, NO) forKey:aKey];
}
// Managing Delegates
/*!
Sets the keyed archiver's delegate
*/
- (void)setDelegate:(id)aDelegate
{
_delegate = aDelegate;
if ([_delegate respondsToSelector:@selector(archiver:didEncodeObject:)])
_delegateSelectors |= _CPKeyedArchiverDidEncodeObjectSelector;
if ([_delegate respondsToSelector:@selector(archiver:willEncodeObject:)])
_delegateSelectors |= _CPKeyedArchiverWillEncodeObjectSelector;
if ([_delegate respondsToSelector:@selector(archiver:willReplaceObject:withObject:)])
_delegateSelectors |= _CPKeyedArchiverWillReplaceObjectWithObjectSelector;
if ([_delegate respondsToSelector:@selector(archiver:didFinishEncoding:)])
_delegateSelectors |= _CPKeyedArchiverDidFinishEncodingSelector;
if ([_delegate respondsToSelector:@selector(archiver:willFinishEncoding:)])
_delegateSelectors |= _CPKeyedArchiverWillFinishEncodingSelector;
}
/*!
Returns the keyed archiver's delegate
*/
- (id)delegate
{
return _delegate;
}
/*!
Encodes a CGPoint
@param aPoint the point to encode
@param aKey the key to associate with the point
*/
- (void)encodePoint:(CGPoint)aPoint forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, CPStringFromPoint(aPoint), NO) forKey:aKey];
}
/*!
Encodes a CGRect
@param aRect the rectangle to encode
@param aKey the key to associate with the rectangle
*/
- (void)encodeRect:(CGRect)aRect forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, CPStringFromRect(aRect), NO) forKey:aKey];
}
/*!
Encodes a CGSize
@param aSize the size to encode
@param aKey the key to associate with the size
*/
- (void)encodeSize:(CGSize)aSize forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, CPStringFromSize(aSize), NO) forKey:aKey];
}
/*!
Encodes an conditionally. The method checks if the object has already been
coded into this data stream before. If so, it will only encode a reference
to that first object to save memory.
@param anObject the object to to conditionally encode
@param aKey the key to associate with the object
*/
- (void)encodeConditionalObject:(id)anObject forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, anObject, YES) forKey:aKey];
}
/*!
Encodes a number
@param aNumber the number to encode
@param aKey the key to associate with the object
*/
- (void)encodeNumber:(CPNumber)aNumber forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, aNumber, NO) forKey:aKey];
}
/*!
Encdoes an object
@param anObject the object to encode
@param aKey the key to associate with the object
*/
- (void)encodeObject:(id)anObject forKey:(CPString)aKey
{
[_plistObject setObject:_CPKeyedArchiverEncodeObject(self, anObject, NO) forKey:aKey];
}
/* @ignore */
- (void)_encodeArrayOfObjects:(CPArray)objects forKey:(CPString)aKey
{
var i = 0,
count = objects.length,
references = [CPArray arrayWithCapacity:count];
for (; i < count; ++i)
[references addObject:_CPKeyedArchiverEncodeObject(self, objects[i], NO)];
[_plistObject setObject:references forKey:aKey];
}
/* @ignore */
- (void)_encodeDictionaryOfObjects:(CPDictionary)aDictionary forKey:(CPString)aKey
{
var key,
keys = [aDictionary keyEnumerator],
references = [CPDictionary dictionary];
while (key = [keys nextObject])
[references setObject:_CPKeyedArchiverEncodeObject(self, [aDictionary objectForKey:key], NO) forKey:key];
[_plistObject setObject:references forKey:aKey];
}
// Managing classes and class names
/*!
Allows substitution of class types for encoding. Specifically classes
of type \c aClass encountered by all keyed archivers will
instead be archived as a class of type \c aClassName.
@param aClassName the substitute class name
@param aClass the class to substitute
*/
+ (void)setClassName:(CPString)aClassName forClass:(Class)aClass
{
if (!CPArchiverReplacementClassNames)
CPArchiverReplacementClassNames = [CPDictionary dictionary];
[CPArchiverReplacementClassNames setObject:aClassName forKey:CPStringFromClass(aClass)];
}
/*!
Returns the name of the substitute class used for encoding
\c aClass by all keyed archivers.
@param aClass the class to substitute
@return the name of the substitute class, or \c nil if there
is no substitute class
*/
+ (CPString)classNameForClass:(Class)aClass
{
if (!CPArchiverReplacementClassNames)
return aClass.name;
var className = [CPArchiverReplacementClassNames objectForKey:CPStringFromClass(aClassName)];
return className ? className : aClass.name;
}
/*!
Allows substitution of class types for encoding. Specifically classes
of type \c aClass encountered by this keyed archiver will
instead be archived as a class of type \c aClassName.
@param aClassName the substitute class name
@param aClass the class to substitute
*/
- (void)setClassName:(CPString)aClassName forClass:(Class)aClass
{
if (!_replacementClassNames)
_replacementClassNames = [CPDictionary dictionary];
[_replacementClassNames setObject:aClassName forKey:CPStringFromClass(aClass)];
}
/*!
Returns the name of the substitute class used for encoding \c aClass by this keyed archiver.
@param aClass the class to substitute
@return the name of the substitute class, or \c nil if there is no substitute class
*/
- (CPString)classNameForClass:(Class)aClass
{
if (!_replacementClassNames)
return aClass.name;
var className = [_replacementClassNames objectForKey:CPStringFromClass(aClassName)];
return className ? className : aClass.name;
}
@end
var _CPKeyedArchiverEncodeObject = function(self, anObject, isConditional)
{
// We wrap primitive JavaScript objects in a unique subclass of CPValue.
// This way, when we unarchive, we know to unwrap it, since
// _CPKeyedArchiverValue should not be used anywhere else.
if (anObject !== nil && !anObject.isa)
anObject = [_CPKeyedArchiverValue valueWithJSObject:anObject];
// Get the proper replacement object
var GUID = [anObject UID],
object = [self._replacementObjects objectForKey:GUID];
// If a replacement object doesn't exist, then actually ask for one.
// Explicitly compare to nil because object could be === 0.
if (object === nil)
{
object = [anObject replacementObjectForKeyedArchiver:self];
// Notify our delegate of this replacement.
if (self._delegate)
{
if (object !== anObject && self._delegateSelectors & _CPKeyedArchiverWillReplaceObjectWithObjectSelector)
[self._delegate archiver:self willReplaceObject:anObject withObject:object];
if (self._delegateSelectors & _CPKeyedArchiverWillEncodeObjectSelector)
{
anObject = [self._delegate archiver:self willEncodeObject:object];
if (anObject !== object && self._delegateSelectors & _CPKeyedArchiverWillReplaceObjectWithObjectSelector)
[self._delegate archiver:self willReplaceObject:object withObject:anObject];
object = anObject;
}
}
[self._replacementObjects setObject:object forKey:GUID];
}
// If we still don't have an object by this point, then return a
// reference to the null object.
// Explicitly compare to nil because object could be === 0.
if (object === nil)
return _CPKeyedArchiverNullReference;
// If not, then grab the object's UID
var UID = [self._UIDs objectForKey:GUID = [object UID]];
// If this object doesn't have a unique index in the object table yet,
// then it also hasn't been properly encoded. We explicitly compare
// index to nil since it could be 0, which would also evaluate to false.
if (UID === nil)
{
// If it is being conditionally encoded, then
if (isConditional)
{
// If we haven't already noted this conditional object...
if ((UID = [self._conditionalUIDs objectForKey:GUID]) === nil)
{
// Use the null object as a placeholder.
[self._conditionalUIDs setObject:UID = [self._plistObjects count] forKey:GUID];
[self._plistObjects addObject:_CPKeyedArchiverNullString];
}
}
else
{
var theClass = [object classForKeyedArchiver],
plistObject = nil;
if ((theClass === _CPKeyedArchiverStringClass) || (theClass === _CPKeyedArchiverNumberClass))// || theClass == _CPKeyedArchiverBooleanClass)
plistObject = object;
else
{
// Only actually encode the object and create a plist representation if it is not a simple type.
plistObject = [CPDictionary dictionary];
[self._objects addObject:object];
var className = [self classNameForClass:theClass];
if (!className)
className = [[self class] classNameForClass:theClass];
if (!className)
className = theClass.name;
else
theClass = CPClassFromString(className);
var classUID = [self._UIDs objectForKey:className];
if (!classUID)
{
var plistClass = [CPDictionary dictionary],
hierarchy = [];
[plistClass setObject:className forKey:_CPKeyedArchiverClassNameKey];
do
{
[hierarchy addObject:CPStringFromClass(theClass)];
} while (theClass = [theClass superclass]);
[plistClass setObject:hierarchy forKey:_CPKeyedArchiverClassesKey];
classUID = [self._plistObjects count];
[self._plistObjects addObject:plistClass];
[self._UIDs setObject:classUID forKey:className];
}
[plistObject setObject:[CPDictionary dictionaryWithObject:classUID forKey:_CPKeyedArchiverUIDKey] forKey:_CPKeyedArchiverClassKey];
}
UID = [self._conditionalUIDs objectForKey:GUID];
// If this object WAS previously encoded conditionally...
if (UID !== nil)
{
[self._UIDs setObject:UID forKey:GUID];
[self._plistObjects replaceObjectAtIndex:UID withObject:plistObject];
}
else
{
[self._UIDs setObject:UID = [self._plistObjects count] forKey:GUID];
[self._plistObjects addObject:plistObject];
}
}
}
return [CPDictionary dictionaryWithObject:UID forKey:_CPKeyedArchiverUIDKey];
}