diff --git a/ChangeLog b/ChangeLog index e7304c70d..0934788b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Sat Oct 24 11:30:00 1998 Richard Frith-Macdonald + + src/NSArchiver.m: New implementation + src/NSArchiver.m: New file + src/GNUmakefile: Added NSUnarchiver.m + src/include/NSArchiver.h: New version + New Archiver/unarchiver - OPENSTEP complient (I think). + Basic functionality tested fairly thoroughly, some of the more + unusual bits untested so far. Mechanisms for adding other backends + in place, but untested. Performance may be around four times that + of the old version though there is still a little (very little) + room for improvement. + Thu Oct 22 21:45:00 1998 Richard Frith-Macdonald src/NSData.m: Added support for serialisation of Class and SEL types diff --git a/Headers/gnustep/base/NSArchiver.h b/Headers/gnustep/base/NSArchiver.h index 4d64dd52a..6ae3cb4de 100644 --- a/Headers/gnustep/base/NSArchiver.h +++ b/Headers/gnustep/base/NSArchiver.h @@ -1,5 +1,5 @@ /* Interface for NSArchiver for GNUStep - Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. + Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. Written by: Andrew Kachites McCallum Date: March 1995 @@ -26,9 +26,40 @@ #include -@class NSMutableData, NSData, NSString; +@class NSMutableDictionary, NSMutableData, NSData, NSString; + +#define _C_NONE 0x00 /* No type information. */ +#define _C_MASK 0x7f /* Basic type info. */ +#define _C_XREF 0x80 /* Cross reference to an item. */ @interface NSArchiver : NSCoder +{ + NSMutableData *data; /* Data to write into. */ + id dst; /* Serialization destination. */ + IMP serImp; /* Method to serialize with. */ + IMP tagImp; /* Serialize a type tag. */ + IMP xRefImp; /* Serialize a crossref. */ + IMP eObjImp; /* Method to encode an id. */ + IMP eValImp; /* Method to encode others. */ +#ifndef _IN_NSARCHIVER_M +#define FastMapTable void* +#endif + FastMapTable clsMap; /* Class cross references. */ + FastMapTable cIdMap; /* Conditionally coded. */ + FastMapTable uIdMap; /* Unconditionally coded. */ + FastMapTable ptrMap; /* Constant pointers. */ + FastMapTable namMap; /* Mappings for class names. */ + FastMapTable repMap; /* Mappings for objects. */ +#ifndef _IN_NSARCHIVER_M +#undef FastMapTable +#endif + unsigned xRefC; /* Counter for cross-reference. */ + unsigned xRefO; /* Counter for cross-reference. */ + unsigned xRefP; /* Counter for cross-reference. */ + unsigned startPos; /* Where in data we started. */ + BOOL isEncodingRootObject; + BOOL isInPreparatoryPass; +} /* Initializing an archiver */ - (id) initForWritingWithMutableData: (NSMutableData*)mdata; @@ -38,19 +69,110 @@ + (BOOL) archiveRootObject: (id)rootObject toFile: (NSString*)path; /* Getting data from the archiver */ -+ unarchiveObjectWithData: (NSData*) data; -+ unarchiveObjectWithFile: (NSString*) path; - (NSMutableData*) archiverData; /* Substituting Classes */ -+ (NSString*) classNameEncodedForTrueClassName: (NSString*) trueName; +- (NSString*) classNameEncodedForTrueClassName: (NSString*) trueName; - (void) encodeClassName: (NSString*)trueName intoClassName: (NSString*)inArchiveName; +/* Substituting Objects */ +- (void) replaceObject: (id)object + withObject: (id)newObject; +@end + +@interface NSArchiver (GNUstep) + +/* + * Re-using the archiver - the 'resetArchiver' method resets the internal + * state of the archiver so that you can re-use it rather than having to + * destroy it and create a new one. + * NB. you would normally want to issue a 'setLength:0' message to the + * mutable data object used by the archiver as well, othewrwise the next + * root object encoded will be appended to data. + */ +- (void) resetArchiver; + +/* + * Subclassing with different output format. + * NSArchiver normally writes directly to an NSMutableData object using + * the methods - + * [-serializeTypeTag:] + * to encode type tags for data items, the tag is the + * first byte of the character encoding string for the + * data type (as provided by '@encode(xxx)'), possibly + * with the top bit set to indicate that what follows is + * a crossreference to an item already encoded. + * [-serializeCrossRef:], + * to encode a crossreference number either to identify the + * following item, or to refer to a previously encoded item. + * Objects, Classes, Selectors, CStrings and Pointer items + * have crossreference encoding, other types do not. + * [-serializeData:ofObjCType:context:] + * to encode all other information. + * + * And uses other NSMutableData methods to write the archive header + * information from within the method: + * [-serializeHeaderAt:version:classes:objects:pointers:] + * to write a fixed size header including archiver version + * (obtained by [self systemVersion]) and crossreference + * table sizes. The archiver will do this twice, once with + * dummy values at initialisation time and once with the real + * values. + * + * To subclass NSArchiver, you must implement your own versions of the + * four methods above, and override the 'directDataAccess' method to + * return NO so that the archiver knows to use your serialization + * methods rather than those in the NSMutableData object. + */ +- (BOOL) directDataAccess; +- (void) serializeHeaderAt: (unsigned)positionInData + version: (unsigned)systemVersion + classes: (unsigned)classCount + objects: (unsigned)objectCount + pointers: (unsigned)pointerCount; + +/* libObjects compatibility */ +- (void) encodeArrayOfObjCType: (const char*) type + count: (unsigned)count + at: (const void*)buf + withName: (id)name; +- (void) encodeIndent; +- (void) encodeValueOfCType: (const char*) type + at: (const void*)buf + withName: (id)name; +- (void) encodeValueOfObjCType: (const char*) type + at: (const void*)buf + withName: (id)name; +- (void) encodeObject: (id)anObject + withName: (id)name; @end + @interface NSUnarchiver : NSCoder +{ + NSData *data; /* Data to write into. */ + Class dataClass; /* What sort of data is it? */ + id src; /* Deserialization source. */ + IMP desImp; /* Method to deserialize with. */ + unsigned char (*tagImp)(id, SEL, unsigned*); + unsigned (*xRefImp)(id, SEL, unsigned*); + IMP dValImp; /* Method to decode data with. */ +#ifndef _IN_NSUNARCHIVER_M +#define GSUnarchiverArray void* +#endif + GSUnarchiverArray clsMap; /* Class crossreference map. */ + GSUnarchiverArray objMap; /* Object crossreference map. */ + GSUnarchiverArray ptrMap; /* Pointer crossreference map. */ +#ifndef _IN_NSUNARCHIVER_M +#undef GSUnarchiverArray +#endif + unsigned cursor; /* Position in data buffer. */ + unsigned version; /* Version of archiver used. */ + NSZone *zone; /* Zone for allocating objs. */ + NSMutableDictionary *objDict; /* Class information store. */ +} /* Initializing an unarchiver */ - (id) initForReadingWithData: (NSData*)data; @@ -73,17 +195,79 @@ - (void) decodeClassName: (NSString*)nameInArchive asClassName: (NSString*)trueName; +/* Substituting objects */ +- (void) replaceObject: (id)anObject withObject: (id)replacement; @end +@interface NSUnarchiver (GNUstep) + +/* + * Re-using the unarchiver - the 'resetUnarchiverWithdata:atIndex:' + * method lets you re-use the archive to decode a new data object + * or, in conjunction with the 'cursor' method (which reports the + * current decoding position in the archive), decode a second + * archive that exists in the data object after the first one. + */ +- (unsigned) cursor; +- (void) resetUnarchiverWithData: (NSData*)data + atIndex: (unsigned)pos; + +/* + * Subclassing with different input format. + * NSUnarchiver normally reads directly from an NSData object using + * the methods - + * [-deserializeTypeTagAtCursor:] + * to decode type tags for data items, the tag is the + * first byte of the character encoding string for the + * data type (as provided by '@encode(xxx)'), possibly + * with the top bit set to indicate that what follows is + * a crossreference to an item already encoded. + * [-deserializeCrossRefAtCursor:], + * to decode a crossreference number either to identify the + * following item, or to refer to a previously encoded item. + * Objects, Classes, Selectors, CStrings and Pointer items + * have crossreference encoding, other types do not. + * [-deserializeData:ofObjCType:atCursor:context:] + * to decode all other information. + * + * And uses other NSData methods to read the archive header information + * from within the method: + * [-deserializeHeaderAt:version:classes:objects:pointers:] + * to read a fixed size header including archiver version + * (obtained by [self systemVersion]) and crossreference + * table sizes. + * + * To subclass NSUnarchiver, you must implement your own versions of the + * four methods above, and override the 'directDataAccess' method to + * return NO so that the archiver knows to use your serialization + * methods rather than those in the NSData object. + */ +- (BOOL) directDataAccess; +- (void) deserializeHeaderAt: (unsigned*)cursor + version: (unsigned*)systemVersion + classes: (unsigned*)classCount + objects: (unsigned*)objectCount + pointers: (unsigned*)pointerCount; + +/* Compatibility with libObjects */ +- (void) decodeArrayOfObjCType: (const char*) type + count: (unsigned)count + at: (void*)buf + withName: (id*)name; +- (void) decodeIndent; +- (void) decodeValueOfCType: (const char*) type + at: (void*)buf + withName: (id*)name; +- (void) decodeValueOfObjCType: (const char*) type + at: (void*)buf + withName: (id*)name; +- (void) decodeObjectAt: (id*)anObject + withName: (id*)name; +@end + + /* Exceptions */ extern NSString *NSInconsistentArchiveException; - -/* NSObject extensions for archiving */ -@interface NSObject (NSArchiver) -- (Class) classForArchiver; -- replacementObjectForArchiver: (NSArchiver*) archiver; -@end - #endif /* __NSArchiver_h_GNUSTEP_BASE_INCLUDE */ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 5cb5927b7..7f0be305e 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -353,6 +353,7 @@ NSTask.m \ NSThread.m \ NSTimer.m \ NSTimeZone.m \ +NSUnarchiver.m \ NSUndoManager.m \ NSUser.m \ NSUserDefaults.m \ diff --git a/Source/NSArchiver.m b/Source/NSArchiver.m index 20da789c9..0bd7bc072 100644 --- a/Source/NSArchiver.m +++ b/Source/NSArchiver.m @@ -1,8 +1,8 @@ -/* archiving class for serialization and persistance. - Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. +/* of NSArchiver for GNUstep + Copyright (C) 1998 Free Software Foundation, Inc. - Written by: Andrew Kachites McCallum - Created: March 1995 + Written by: Richard frith-Macdonald + Created: October 1998 This file is part of the GNUstep Base Library. @@ -22,229 +22,976 @@ */ #include -#include +#include +/* + * Setup for inline operation of pointer map tables. + */ +#define FAST_MAP_RETAIN_KEY(X) X +#define FAST_MAP_RELEASE_KEY(X) +#define FAST_MAP_RETAIN_VAL(X) X +#define FAST_MAP_RELEASE_VAL(X) +#define FAST_MAP_HASH(X) ((X).I) +#define FAST_MAP_EQUAL(X,Y) ((X).I == (Y).I) + +#include "FastMap.x" + +#define _IN_NSARCHIVER_M #include -#include +#undef _IN_NSARCHIVER_M + +#include +#include #include -#include +#include +#include +#include + +#include + +typedef unsigned char uchar; + + +#define PREFIX "GNUstep archive" + +static SEL serSel = @selector(serializeDataAt:ofObjCType:context:); +static SEL tagSel = @selector(serializeTypeTag:); +static SEL xRefSel = @selector(serializeCrossRef:); +static SEL eObjSel = @selector(encodeObject:); +static SEL eValSel = @selector(encodeValueOfObjCType:at:); @implementation NSArchiver -static Class NSArchiver_concrete_class; - -+ (void) _setConcreteClass: (Class)c +- (id) init { - NSArchiver_concrete_class = c; + return [self initForWritingWithMutableData: + [[_fastCls._NSMutableDataMalloc allocWithZone: [self zone]] init]]; } -+ (Class) _concreteClass +- (id) initForWritingWithMutableData: (NSMutableData*)anObject { - return NSArchiver_concrete_class; -} + self = [super init]; + if (self) + { + NSZone *zone = [self zone]; -+ (void) initialize -{ - if (self == [NSArchiver class]) - NSArchiver_concrete_class = [NSGArchiver class]; -} + data = [anObject retain]; + if ([self directDataAccess] == YES) + { + dst = data; + } + else + { + dst = self; + } + serImp = [dst methodForSelector: serSel]; + tagImp = [dst methodForSelector: tagSel]; + xRefImp = [dst methodForSelector: xRefSel]; + eObjImp = [self methodForSelector: eObjSel]; + eValImp = [self methodForSelector: eValSel]; - -/* Allocating and Initializing an archiver */ + [self resetArchiver]; -+ allocWithZone:(NSZone *)zone -{ - return NSAllocateObject([self _concreteClass], 0, zone); -} - -/* This is the designated initializer */ -- (id) initForWritingWithMutableData: (NSMutableData*)mdata -{ - [self subclassResponsibility:_cmd]; + /* + * Set up map tables. + */ + clsMap = (FastMapTable)NSZoneMalloc(zone, sizeof(FastMapTable_t)*6); + cIdMap = &clsMap[1]; + uIdMap = &clsMap[2]; + ptrMap = &clsMap[3]; + namMap = &clsMap[4]; + repMap = &clsMap[5]; + FastMapInitWithZoneAndCapacity(clsMap, zone, 100); + FastMapInitWithZoneAndCapacity(cIdMap, zone, 10); + FastMapInitWithZoneAndCapacity(uIdMap, zone, 200); + FastMapInitWithZoneAndCapacity(ptrMap, zone, 100); + FastMapInitWithZoneAndCapacity(namMap, zone, 1); + FastMapInitWithZoneAndCapacity(repMap, zone, 1); + } return self; } - - - -/* Archiving Data */ - -- init +- (void) dealloc { - [self subclassResponsibility:_cmd]; - return self; + [data release]; + if (clsMap) + { + FastMapEmptyMap(clsMap); + if (cIdMap) + { + FastMapEmptyMap(cIdMap); + } + if (uIdMap) + { + FastMapEmptyMap(uIdMap); + } + if (ptrMap) + { + FastMapEmptyMap(ptrMap); + } + if (namMap) + { + FastMapEmptyMap(namMap); + } + if (repMap) + { + FastMapEmptyMap(repMap); + } + NSZoneFree(clsMap->zone, (void*)clsMap); + } + return [super dealloc]; } + (NSData*) archivedDataWithRootObject: (id)rootObject { - id d = [[NSMutableData alloc] init]; - id a = [[NSArchiver alloc] initForWritingWithMutableData:d]; - [a encodeRootObject:rootObject]; - [a release]; /* Done with archiver. */ - return [d autorelease]; + NSArchiver *archiver; + id d; + NSZone *z = NSDefaultMallocZone(); + + d = [[_fastCls._NSMutableDataMalloc allocWithZone: z] initWithCapacity: 0]; + if (d == nil) + { + return nil; + } + archiver = [[self allocWithZone: z] initForWritingWithMutableData: d]; + if (archiver) + { + NS_DURING + { + [archiver encodeRootObject: rootObject]; + d = [[archiver->data copy] autorelease]; + } + NS_HANDLER + { + d = nil; + } + NS_ENDHANDLER + [archiver release]; + } + else + { + d = nil; + } + + return d; } -+ (BOOL) archiveRootObject: (id)rootObject toFile: (NSString*)path ++ (BOOL) archiveRootObject: (id)rootObject + toFile: (NSString*)path { - /* xxx fix this return value */ - id d = [self archivedDataWithRootObject:rootObject]; - [d writeToFile:path atomically:NO]; - return YES; + id d = [self archivedDataWithRootObject: rootObject]; + + return [d writeToFile: path atomically: YES]; } -- (unsigned int) versionForClassName: (NSString*)className; +- (void) encodeArrayOfObjCType: (const char*)type + count: (unsigned)count + at: (const void*)buf { - [self subclassResponsibility:_cmd]; - return 0; + unsigned i; + unsigned offset = 0; + unsigned size = objc_sizeof_type(type); + uchar info; + + switch (*type) + { + case _C_ID: info = _C_NONE; break; + case _C_CHR: info = _C_CHR; break; + case _C_UCHR: info = _C_UCHR; break; + case _C_SHT: info = _C_SHT; break; + case _C_USHT: info = _C_USHT; break; + case _C_INT: info = _C_INT; break; + case _C_UINT: info = _C_UINT; break; + case _C_LNG: info = _C_LNG; break; + case _C_ULNG: info = _C_ULNG; break; +#ifdef _C_LNG_LNG + case _C_LNG_LNG: info = _C_LNG_LNG; break; + case _C_ULNG_LNG: info = _C_ULNG_LNG; break; +#endif + case _C_FLT: info = _C_FLT; break; + case _C_DBL: info = _C_DBL; break; + default: info = _C_NONE; break; + } + + /* + * Simple types can be serialized immediately, more complex ones + * are dealt with by our [encodeValueOfObjCType:at:] method. + */ + if (info == _C_NONE) + { + if (isInPreparatoryPass == NO) + { + (*tagImp)(dst, tagSel, _C_ARY_B); + (*serImp)(dst, serSel, &count, @encode(unsigned), nil); + } + for (i = 0; i < count; i++) + { + (*eValImp)(self, eValSel, type, (char*)buf + offset); + offset += size; + } + } + else if (isInPreparatoryPass == NO) + { + (*tagImp)(dst, tagSel, _C_ARY_B); + (*serImp)(dst, serSel, &count, @encode(unsigned), nil); + + (*tagImp)(dst, tagSel, info); + for (i = 0; i < count; i++) + { + (*serImp)(dst, serSel, (char*)buf + offset, type, nil); + offset += size; + } + } } - -/* Getting data from the archiver */ - -+ unarchiveObjectWithData: (NSData*) data +- (void) encodeValueOfObjCType: (const char*)type + at: (const void*)buf { - return [[self _concreteClass] unarchiveObjectWithData: data]; + uchar info; + + switch (*type) + { + case _C_ID: + (*eObjImp)(self, eObjSel, *(void**)buf); + return; + + case _C_ARY_B: + { + int count = atoi(++type); + + while (isdigit(*type)) + { + type++; + } + + if (isInPreparatoryPass == NO) + { + (*tagImp)(dst, tagSel, _C_ARY_B); + } + [self encodeArrayOfObjCType: type count: count at: buf]; + } + return; + + case _C_STRUCT_B: + { + int offset = 0; + + if (isInPreparatoryPass == NO) + { + (*tagImp)(dst, tagSel, _C_STRUCT_B); + } + + while (*type != _C_STRUCT_E && *type != '=') + { + type++; + } + + for (;;) + { + (*eValImp)(self, eValSel, type, (char*)buf + offset); + offset += objc_sizeof_type(type); + type = objc_skip_typespec(type); + if (*type == _C_STRUCT_E) + { + break; + } + else + { + int align = objc_alignof_type(type); + int rem = offset % align; + + if (rem != 0) + { + offset += align - rem; + } + } + } + } + return; + + case _C_PTR: + if (*(void**)buf == 0) + { + if (isInPreparatoryPass == NO) + { + /* + * Special case - a nul pointer gets an xref of zero + */ + (*tagImp)(dst, tagSel, _C_PTR | _C_XREF); + (*xRefImp)(dst, xRefSel, 0); + } + } + else + { + FastMapNode node; + + node = FastMapNodeForKey(ptrMap, (FastMapItem)*(void**)buf); + if (isInPreparatoryPass == YES) + { + /* + * First pass - add pointer to map and encode item pointed + * to in case it is a conditionally encoded object. + */ + if (node == 0) + { + FastMapAddPair(ptrMap, + (FastMapItem)*(void**)buf, (FastMapItem)0); + type++; + buf = *(char**)buf; + (*eValImp)(self, eValSel, type, buf); + } + } + else if (node == 0 || node->value.I == 0) + { + /* + * Second pass, unwritten pointer - write it. + */ + if (node == 0) + { + node = FastMapAddPair(ptrMap, + (FastMapItem)*(void**)buf, (FastMapItem)++xRefP); + } + else + { + node->value.I = ++xRefP; + } + (*tagImp)(dst, tagSel, _C_PTR); + type++; + buf = *(char**)buf; + (*eValImp)(self, eValSel, type, buf); + } + else + { + /* + * Second pass, write a cross-reference number. + */ + (*tagImp)(dst, tagSel, _C_PTR | _C_XREF); + (*xRefImp)(dst, xRefSel, node->value.I); + } + } + return; + + default: /* Types that can be ignored in first pass. */ + if (isInPreparatoryPass) + { + return; + } + break; + } + + switch (*type) + { + case _C_CLASS: + if (*(Class*)buf == 0) + { + /* + * Special case - a nul pointer gets an xref of zero + */ + (*tagImp)(dst, tagSel, _C_CLASS | _C_XREF); + (*xRefImp)(dst, xRefSel, 0); + } + else + { + Class c = *(Class*)buf; + FastMapNode node; + BOOL done = NO; + + node = FastMapNodeForKey(clsMap, (FastMapItem)(void*)c); + + if (node != 0) + { + (*tagImp)(dst, tagSel, _C_CLASS | _C_XREF); + (*xRefImp)(dst, xRefSel, node->value.I); + return; + } + while (done == NO) + { + int tmp = fastClassVersion(c); + unsigned version = tmp; + Class s = fastSuper(c); + + if (tmp < 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"negative class version"]; + } + node = FastMapAddPair(clsMap, + (FastMapItem)(void*)c, (FastMapItem)++xRefC); + /* + * Encode tag and crossref number. + */ + (*tagImp)(dst, tagSel, _C_CLASS); + (*xRefImp)(dst, xRefSel, node->value.I); + /* + * Encode class, and version. + */ + (*serImp)(dst, serSel, &c, @encode(Class), nil); + (*serImp)(dst, serSel, &version, @encode(unsigned), nil); + /* + * If we have a super class that has not been encoded, + * we must loop round to encode it here so that its + * version information will be available when objects + * of its subclasses are decoded and call + * [super initWithCoder:ccc] + */ + if (s == c || s == 0 || + FastMapNodeForKey(clsMap, (FastMapItem)(void*)s) != 0) + { + done = YES; + } + else + { + c = s; + } + } + /* + * Encode an empty tag to terminate the list of classes. + */ + (*tagImp)(dst, tagSel, _C_NONE); + } + return; + + case _C_SEL: + if (*(SEL*)buf == 0) + { + /* + * Special case - a nul pointer gets an xref of zero + */ + (*tagImp)(dst, tagSel, _C_SEL | _C_XREF); + (*xRefImp)(dst, xRefSel, 0); + } + else + { + SEL s = *(SEL*)buf; + FastMapNode node = FastMapNodeForKey(ptrMap, (FastMapItem)(void*)s); + + if (node == 0) + { + node = FastMapAddPair(ptrMap, + (FastMapItem)(void*)s, (FastMapItem)++xRefP); + (*tagImp)(dst, tagSel, _C_SEL); + (*xRefImp)(dst, xRefSel, node->value.I); + /* + * Encode selector. + */ + (*serImp)(dst, serSel, buf, @encode(SEL), nil); + } + else + { + (*tagImp)(dst, tagSel, _C_SEL | _C_XREF); + (*xRefImp)(dst, xRefSel, node->value.I); + } + } + return; + + case _C_CHARPTR: + if (*(char**)buf == 0) + { + /* + * Special case - a nul pointer gets an xref of zero + */ + (*tagImp)(dst, tagSel, _C_CHARPTR | _C_XREF); + (*xRefImp)(dst, xRefSel, 0); + } + else + { + FastMapNode node; + + node = FastMapNodeForKey(ptrMap, (FastMapItem)*(char**)buf); + if (node == 0) + { + node = FastMapAddPair(ptrMap, + (FastMapItem)*(char**)buf, (FastMapItem)++xRefP); + (*tagImp)(dst, tagSel, _C_CHARPTR); + (*xRefImp)(dst, xRefSel, node->value.I); + (*serImp)(dst, serSel, buf, type, nil); + } + else + { + (*tagImp)(dst, tagSel, _C_CHARPTR | _C_XREF); + (*xRefImp)(dst, xRefSel, node->value.I); + } + } + return; + + case _C_CHR: + (*tagImp)(dst, tagSel, _C_CHR); + (*serImp)(dst, serSel, (void*)buf, @encode(char), nil); + return; + + case _C_UCHR: + (*tagImp)(dst, tagSel, _C_UCHR); + (*serImp)(dst, serSel, (void*)buf, @encode(unsigned char), nil); + return; + + case _C_SHT: + (*tagImp)(dst, tagSel, _C_SHT); + (*serImp)(dst, serSel, (void*)buf, @encode(short), nil); + return; + + case _C_USHT: + (*tagImp)(dst, tagSel, _C_USHT); + (*serImp)(dst, serSel, (void*)buf, @encode(unsigned short), nil); + return; + + case _C_INT: + (*tagImp)(dst, tagSel, _C_INT); + (*serImp)(dst, serSel, (void*)buf, @encode(int), nil); + return; + + case _C_UINT: + (*tagImp)(dst, tagSel, _C_UINT); + (*serImp)(dst, serSel, (void*)buf, @encode(unsigned int), nil); + return; + + case _C_LNG: + (*tagImp)(dst, tagSel, _C_LNG); + (*serImp)(dst, serSel, (void*)buf, @encode(long), nil); + return; + + case _C_ULNG: + (*tagImp)(dst, tagSel, _C_ULNG); + (*serImp)(dst, serSel, (void*)buf, @encode(unsigned long), nil); + return; + +#ifdef _C_LNG_LNG + case _C_LNG_LNG: + (*tagImp)(dst, tagSel, _C_LNG_LNG); + (*serImp)(dst, serSel, (void*)buf, @encode(long long), nil); + return; + + case _C_ULNG_LNG: + (*tagImp)(dst, tagSel, _C_ULNG_LNG); + (*serImp)(dst, serSel, (void*)buf, @encode(unsigned long long), nil); + return; + +#endif + case _C_FLT: + (*tagImp)(dst, tagSel, _C_FLT); + (*serImp)(dst, serSel, (void*)buf, @encode(float), nil); + return; + + case _C_DBL: + (*tagImp)(dst, tagSel, _C_DBL); + (*serImp)(dst, serSel, (void*)buf, @encode(double), nil); + return; + + case _C_VOID: + [NSException raise: NSInvalidArgumentException + format: @"can't encode void item"]; + + default: + [NSException raise: NSInvalidArgumentException + format: @"item with unknown type - %s", type]; + } } -+ unarchiveObjectWithFile: (NSString*) path +- (void) encodeRootObject: (id)rootObject { - return [[self _concreteClass] unarchiveObjectWithFile: path]; + if (isEncodingRootObject) + { + [NSException raise: NSInvalidArgumentException + format: @"encoding root object more than once"]; + } + + isEncodingRootObject = YES; + + /* + * First pass - find conditional objects. + */ + isInPreparatoryPass = YES; + (*eObjImp)(self, eObjSel, rootObject); + + /* + * Second pass - write archive. + */ + isInPreparatoryPass = NO; + (*eObjImp)(self, eObjSel, rootObject); + + /* + * Write sizes of crossref arrays to head of archive. + */ + [self serializeHeaderAt: startPos + version: [self systemVersion] + classes: clsMap->nodeCount + objects: uIdMap->nodeCount + pointers: ptrMap->nodeCount]; + + isEncodingRootObject = NO; +} + +- (void) encodeConditionalObject: (id)anObject +{ + if (isEncodingRootObject == NO) + { + [NSException raise: NSInvalidArgumentException + format: @"conditionally encoding without root object"]; + return; + } + + if (isInPreparatoryPass) + { + FastMapNode node; + + /* + * Conditionally encoding 'nil' is a no-op. + */ + if (anObject == nil) + { + return; + } + + /* + * If we have already conditionally encoded this object, we can + * ignore it this time. + */ + node = FastMapNodeForKey(cIdMap, (FastMapItem)anObject); + if (node != 0) + { + return; + } + + /* + * If we have unconditionally encoded this object, we can ignore + * it now. + */ + node = FastMapNodeForKey(uIdMap, (FastMapItem)anObject); + if (node != 0) + { + return; + } + + FastMapAddPair(cIdMap, (FastMapItem)anObject, (FastMapItem)0); + } + else if (anObject == nil) + { + (*eObjImp)(self, eObjSel, nil); + } + else + { + FastMapNode node; + + if (repMap->nodeCount) + { + node = FastMapNodeForKey(repMap, (FastMapItem)anObject); + if (node) + { + anObject = (id)node->value.p; + } + } + + node = FastMapNodeForKey(cIdMap, (FastMapItem)anObject); + if (node != 0) + { + (*eObjImp)(self, eObjSel, nil); + } + else + { + (*eObjImp)(self, eObjSel, anObject); + } + } +} + +- (void) encodeObject: (id)anObject +{ + if (anObject == nil) + { + if (isInPreparatoryPass == NO) + { + /* + * Special case - encode a nil pointer as a crossref of zero. + */ + (*tagImp)(dst, tagSel, _C_ID | _C_XREF); + (*xRefImp)(dst, xRefSel, 0); + } + } + else if (fastIsInstance(anObject) == NO) + { + /* + * If the object we have been given is actually a class, + * we encode it as a class instead. + */ + (*eValImp)(self, eValSel, @encode(Class), &anObject); + } + else + { + FastMapNode node; + + /* + * Substitute replacement object if required. + */ + node = FastMapNodeForKey(repMap, (FastMapItem)anObject); + if (node) + { + anObject = (id)node->value.p; + } + + /* + * See if the object has already been encoded. + */ + node = FastMapNodeForKey(uIdMap, (FastMapItem)anObject); + + if (isInPreparatoryPass) + { + if (node == 0) + { + /* + * Remove object from map of conditionally encoded objects + * and add it to the map of unconditionay encoded ones. + */ + FastMapRemoveKey(cIdMap, (FastMapItem)anObject); + FastMapAddPair(uIdMap, (FastMapItem)anObject, (FastMapItem)0); + [anObject encodeWithCoder: self]; + } + return; + } + + if (node == 0 || node->value.I == 0) + { + Class cls; + id obj; + uchar info = _C_ID; + + if (node == 0) + { + node = FastMapAddPair(uIdMap, + (FastMapItem)anObject, (FastMapItem)++xRefO); + } + else + { + node->value.I = ++xRefO; + } + + obj = [anObject replacementObjectForArchiver: self]; + cls = [anObject classForArchiver]; + + (*tagImp)(dst, tagSel, _C_ID); + (*xRefImp)(dst, xRefSel, node->value.I); + if (namMap->nodeCount) + { + FastMapNode node; + + node = FastMapNodeForKey(namMap, (FastMapItem)cls); + + if (node) + { + cls = (Class)node->value.p; + } + } + (*eValImp)(self, eValSel, @encode(Class), &cls); + [obj encodeWithCoder: self]; + } + else if(!isInPreparatoryPass) + { + (*tagImp)(dst, tagSel, _C_ID | _C_XREF); + (*xRefImp)(dst, xRefSel, node->value.I); + } + } } - (NSMutableData*) archiverData { - [self subclassResponsibility:_cmd]; - return nil; + return data; } - -/* Substituting Classes */ - -+ (NSString*) classNameEncodedForTrueClassName: (NSString*)trueName +- (NSString*) classNameEncodedForTrueClassName:(NSString*)trueName { - return [[self _concreteClass] classNameEncodedForTrueClassName: trueName]; + if (namMap->nodeCount) + { + FastMapNode node; + Class c; + + c = objc_get_class([trueName cString]); + node = FastMapNodeForKey(namMap, (FastMapItem)c); + if (node) + { + c = (Class)node->value.p; + return [NSString stringWithCString: fastClassName(c)]; + } + } + return trueName; } - (void) encodeClassName: (NSString*)trueName - intoClassName: (NSString*)inArchiveName + intoClassName: (NSString*)inArchiveName { - [self subclassResponsibility:_cmd]; + FastMapNode node; + Class tc; + Class ic; + + tc = objc_get_class([trueName cString]); + if (tc == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"Can't find class '%@'.", trueName]; + } + ic = objc_get_class([inArchiveName cString]); + if (ic == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"Can't find class '%@'.", inArchiveName]; + } + node = FastMapNodeForKey(namMap, (FastMapItem)tc); + if (node == 0) + { + FastMapAddPair(namMap, (FastMapItem)(void*)tc, (FastMapItem)(void*)ic); + } + else + { + node->value.p = (void*)ic; + } } +- (void) replaceObject: (id)object + withObject: (id)newObject +{ + FastMapNode node; + + if (object == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"attempt to remap nil"]; + } + if (newObject == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"attempt to remap object to nil"]; + } + node = FastMapNodeForKey(namMap, (FastMapItem)object); + if (node == 0) + { + FastMapAddPair(namMap, (FastMapItem)object, (FastMapItem)newObject); + } + else + { + node->value.p = (void*)newObject; + } +} @end -@implementation NSUnarchiver -static Class NSUnarchiver_concrete_class; +/* + * Catagories for compatibility with old GNUstep encoding. + */ -+ (void) _setConcreteClass: (Class)c +@implementation NSArchiver (GNUstep) + +/* Re-using an archiver */ + +- (void) resetArchiver { - NSUnarchiver_concrete_class = c; + char buf[strlen(PREFIX)+33]; + + if (clsMap) + { + FastMapCleanMap(clsMap); + if (cIdMap) + { + FastMapCleanMap(cIdMap); + } + if (uIdMap) + { + FastMapCleanMap(uIdMap); + } + if (ptrMap) + { + FastMapCleanMap(ptrMap); + } + if (namMap) + { + FastMapCleanMap(namMap); + } + if (repMap) + { + FastMapCleanMap(repMap); + } + } + isEncodingRootObject = NO; + isInPreparatoryPass = NO; + xRefC = 0; + xRefO = 0; + xRefP = 0; + + /* + * Write dummy header + */ + startPos = [data length]; + [self serializeHeaderAt: startPos + version: 0 + classes: 0 + objects: 0 + pointers: 0]; } -+ (Class) _concreteClass +- (BOOL) directDataAccess { - return NSUnarchiver_concrete_class; + return YES; } -+ (void) initialize +- (void) serializeHeaderAt: (unsigned)locationInData + version: (unsigned)v + classes: (unsigned)cc + objects: (unsigned)oc + pointers: (unsigned)pc { - if (self == [NSUnarchiver class]) - NSUnarchiver_concrete_class = [NSGUnarchiver class]; + unsigned headerLength = strlen(PREFIX)+36; + char header[headerLength+1]; + unsigned dataLength = [data length]; + + sprintf(header, "%s%08x:%08x:%08x:%08x:", PREFIX, v, cc, oc, pc); + + if (locationInData + headerLength <= dataLength) + { + [data replaceBytesInRange: NSMakeRange(locationInData, headerLength) + withBytes: header]; + } + else if (locationInData == dataLength) + { + [data appendBytes: header length: headerLength]; + } + else + { + [NSException raise: NSInternalInconsistencyException + format: @"serializeHeader:at: bad location"]; + } } -// Initializing an unarchiver - -+ allocWithZone:(NSZone *)zone +/* libObjects compatibility */ + +- (void) encodeArrayOfObjCType: (const char*) type + count: (unsigned)count + at: (const void*)buf + withName: (id)name { - return NSAllocateObject([self _concreteClass], 0, zone); + (*eObjImp)(self, eObjSel, name); + [self encodeArrayOfObjCType: type count: count at: buf]; } -- (id) initForReadingWithData: (NSData*)data +- (void) encodeIndent { - [self subclassResponsibility:_cmd]; - return nil; } -// Decoding objects - -+ (id) unarchiveObjectWithData: (NSData*)data +- (void) encodeValueOfCType: (const char*) type + at: (const void*)buf + withName: (id)name { - return [[self _concreteClass] unarchiveObjectWithData: data]; + (*eObjImp)(self, eObjSel, name); + (*eValImp)(self, eValSel, type, buf); } -+ (id) unarchiveObjectWithFile: (NSString*)path +- (void) encodeValueOfObjCType: (const char*) type + at: (const void*)buf + withName: (id)name { - return [[self _concreteClass] unarchiveObjectWithFile: path]; + (*eObjImp)(self, eObjSel, name); + (*eValImp)(self, eValSel, type, buf); } -/* Managing */ - -- (BOOL) isAtEnd +- (void) encodeObject: (id)anObject + withName: (id)name { - [self subclassResponsibility:_cmd]; - return NO; + (*eObjImp)(self, eObjSel, name); + (*eObjImp)(self, eObjSel, anObject); } - -- (NSZone*) objectZone -{ - [self subclassResponsibility:_cmd]; - return NULL; -} - -- (void) setObjectZone: (NSZone*)zone -{ - [self subclassResponsibility:_cmd]; -} - -- (unsigned int) systemVersion -{ - [self subclassResponsibility:_cmd]; - return 0; -} - -/* Substituting Classes */ - -+ (NSString*) classNameDecodedForArchiveClassName: (NSString*)nameInArchive -{ - return [[self _concreteClass] - classNameDecodedForArchiveClassName: nameInArchive]; -} -+ (void) decodeClassName: (NSString*)nameInArchive - asClassName: (NSString*)trueName -{ - return [[self _concreteClass] - decodeClassName: nameInArchive asClassName: trueName]; -} - -- (NSString*) classNameDecodedForArchiveClassName: (NSString*)nameInArchive -{ - [self subclassResponsibility:_cmd]; - return nil; -} - -- (void) decodeClassName: (NSString*)nameInArchive - asClassName: (NSString*)trueName -{ - [self subclassResponsibility:_cmd]; -} - -@end - - -/* NSObject extensions for archiving */ - -@implementation NSObject (NSArchiver) - -- (Class) classForArchiver -{ - return [self classForCoder]; -} - -- replacementObjectForArchiver: (NSArchiver*) archiver -{ - return [self replacementObjectForCoder: archiver]; -} - @end diff --git a/Source/NSUnarchiver.m b/Source/NSUnarchiver.m new file mode 100644 index 000000000..af248ee38 --- /dev/null +++ b/Source/NSUnarchiver.m @@ -0,0 +1,1226 @@ +/* of NSUnarchiver for GNUstep + Copyright (C) 1998 Free Software Foundation, Inc. + + Written by: Richard frith-Macdonald + Created: October 1998 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#define _IN_NSUNARCHIVER_M +struct GSUnarchiverArrayStruct { + void **ptr; + unsigned count; + unsigned cap; + unsigned old; + NSZone *zone; +}; + +typedef struct GSUnarchiverArrayStruct GSUnarchiverArray_t; +typedef GSUnarchiverArray_t *GSUnarchiverArray; + +#include +#undef _IN_NSUNARCHIVER_M + +#include +#include +#include +#include +#include +#include + +#include + +typedef unsigned char uchar; + +static const char* +typeToName(char type) +{ + switch (type & _C_MASK) + { + case _C_CLASS: return "class"; + case _C_ID: return "object"; + case _C_SEL: return "selector"; + case _C_CHR: return "char"; + case _C_UCHR: return "unsigned char"; + case _C_SHT: return "short"; + case _C_USHT: return "unsigned short"; + case _C_INT: return "int"; + case _C_UINT: return "unsigned int"; + case _C_LNG: return "long"; + case _C_ULNG: return "unsigned long"; +#ifdef _C_LNG_LNG + case _C_LNG_LNG: return "long long"; + case _C_ULNG_LNG: return "unsigned long long"; +#endif + case _C_FLT: return "float"; + case _C_DBL: return "double"; + case _C_PTR: return "pointer"; + case _C_CHARPTR: return "cstring"; + case _C_ARY_B: return "array"; + case _C_STRUCT_B: return "struct"; + default: + { + static char buf1[32]; + static char buf2[32]; + static char *bufptr = buf1; + + if (bufptr == buf1) + { + bufptr = buf2; + } + else + { + bufptr = buf1; + } + sprintf(bufptr, "unknown type info - 0x%x", type); + return bufptr; + } + } +} + +static inline void +typeCheck(char t1, char t2) +{ + if (t1 != t2) + { + [NSException raise: NSInternalInconsistencyException + format: @"expected %s and got %s", + typeToName(t1), typeToName(t2)]; + } +} + +#define PREFIX "GNUstep archive" + +static inline void +arrayAddItem(GSUnarchiverArray array, void* value) +{ + if (array->count == array->cap) + { + unsigned next; + void **tmp; + + next = array->cap + array->old; + tmp = NSZoneRealloc(array->zone, array->ptr, next*sizeof(void*)); + + if (tmp == 0) + { + [NSException raise: NSMallocException + format: @"failed to grow unarchiver crossref array"]; + } + array->ptr = tmp; + array->old = array->cap; + array->cap = next; + } + array->ptr[array->count++] = value; +} + + +static SEL desSel = @selector(deserializeDataAt:ofObjCType:atCursor:context:); +static SEL tagSel = @selector(deserializeTypeTagAtCursor:); +static SEL xRefSel = @selector(deserializeCrossRefAtCursor:); +static SEL dValSel = @selector(decodeValueOfObjCType:at:); +static SEL aDatSel = @selector(allocWithZone:); +static SEL iDatSel = @selector(initWithBytesNoCopy:length:fromZone:); +static SEL rDatSel = @selector(autorelease); + +@interface _GSClsInfo : NSObject +{ + NSString *original; + NSString *name; + Class class; +} ++ (id) newWithName: (NSString*)n; +- (NSString*) className; +- (Class) classObject; +- (void) mapToClass: (Class)c withName: (NSString*)name; +@end + +@implementation _GSClsInfo ++ (id) newWithName: (NSString*)n +{ + _GSClsInfo *info = [_GSClsInfo alloc]; + + if (info) + { + info->original = [n copy]; + } + return info; +} +- (NSString*) className +{ + return name; +} +- (Class) classObject +{ + return class; +} +- (void) dealloc +{ + [original release]; + [name release]; + [super dealloc]; +} +- (void) mapToClass: (Class)c withName: (NSString*)n +{ + [n retain]; + [name release]; + name = n; + class = c; +} +@end + +/* + * Dictionary used by NSUnarchiver class to keep track of _GSClsInfo + * objects used to map classes by name when unarchiving. + */ +static NSMutableDictionary *clsDict; /* Class information */ + +@interface _GSObjInfo : _GSClsInfo +{ + unsigned version; + _GSClsInfo *overrides; +} +- (void) setVersion: (unsigned)v; +- (unsigned) version; +@end + +@implementation _GSObjInfo ++ (id) newWithName: (NSString*)n +{ + _GSObjInfo *info = [_GSObjInfo alloc]; + + if (info) + { + info->original = [n copy]; + } + return info; +} +- (NSString*) className +{ + if (overrides == nil) + { + overrides = [clsDict objectForKey: original]; + } + if (overrides) + { + return [overrides className]; + } + return name; +} +- (Class) classObject +{ + if (overrides == nil) + { + overrides = [clsDict objectForKey: original]; + } + if (overrides) + { + return [overrides classObject]; + } + return class; +} +- (void) setVersion: (unsigned)v +{ + version = v; +} +- (unsigned) version +{ + return version; +} +@end + + +@implementation NSUnarchiver + +static IMP aDatImp; /* To allocate a data object. */ +static IMP iDatImp; /* To initialise the data. */ +static IMP rDatImp; /* To autorelease it. */ + ++ (void) initialize +{ + if ([self class] == [NSUnarchiver class]) + { + clsDict = [[NSMutableDictionary alloc] initWithCapacity: 200]; + /* + * Grab method implementations so we can create NSData objects to + * hold temporary memory for us. If we have lots of strings etc + * to unarchive, the performance of NSData creation is important. + */ + aDatImp = [_fastCls._NSDataMalloc methodForSelector: aDatSel]; + iDatImp = [_fastCls._NSDataMalloc instanceMethodForSelector: iDatSel]; + rDatImp = [_fastCls._NSDataMalloc instanceMethodForSelector: rDatSel]; + } +} + ++ (id) unarchiveObjectWithData: (NSData*)anObject +{ + NSUnarchiver *unarchiver; + id obj; + + unarchiver = [[self alloc] initForReadingWithData: anObject]; + NS_DURING + { + obj = [unarchiver decodeObject]; + } + NS_HANDLER + { + obj = nil; + } + NS_ENDHANDLER + [unarchiver release]; + + return obj; +} + ++ (id) unarchiveObjectWithFile: (NSString*)path +{ + NSData *d = [_fastCls._NSDataMalloc dataWithContentsOfFile: path]; + + if (d != nil) + { + return [self unarchiveObjectWithData: d]; + } + return nil; +} + +- (void) dealloc +{ + [data release]; + [objDict release]; + if (clsMap) + { + if (clsMap->ptr) + { + NSZoneFree(clsMap->zone, clsMap->ptr); + } + if (objMap->ptr) + { + NSZoneFree(objMap->zone, objMap->ptr); + } + if (ptrMap->ptr) + { + NSZoneFree(ptrMap->zone, ptrMap->ptr); + } + NSZoneFree(clsMap->zone, (void*)clsMap); + } + [super dealloc]; +} + +- (id) initForReadingWithData: (NSData*)anObject +{ + if (anObject == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"nil data passed to initForReadingWithData:"]; + } + + self = [super init]; + if (self) + { + dValImp = [self methodForSelector: dValSel]; + zone = [self zone]; + /* + * If we are not deserializing directly from the data object + * then we cache our own deserialisation methods. + */ + if ([self directDataAccess] == NO) + { + src = self; /* Default object to handle serialisation */ + desImp = [src methodForSelector: desSel]; + tagImp = (unsigned char (*)(id, SEL, unsigned*)) + [src methodForSelector: tagSel]; + xRefImp = (unsigned (*)(id, SEL, unsigned*)) + [src methodForSelector: xRefSel]; + } + /* + * objDict is a dictionary of objects for mapping classes of + * one name to be those of another name! It also handles + * keeping track of the version numbers that the classes were + * encoded with. + */ + objDict = [[NSMutableDictionary allocWithZone: zone] + initWithCapacity: 200]; + + [self resetUnarchiverWithData: anObject atIndex: 0]; + } + return self; +} + +- (void) decodeArrayOfObjCType: (const char*)type + count: (unsigned)expected + at: (void*)buf +{ + int i; + int offset = 0; + int size = objc_sizeof_type(type); + uchar info; + unsigned count; + + info = (*tagImp)(src, tagSel, &cursor); + (*desImp)(src, desSel, &count, @encode(unsigned), &cursor, nil); + if (info != _C_ARY_B) + { + [NSException raise: NSInternalInconsistencyException + format: @"expected array and got %s", typeToName(info)]; + } + if (count != expected) + { + [NSException raise: NSInternalInconsistencyException + format: @"expected array count %u and got %u", + expected, count]; + } + + switch (*type) + { + case _C_ID: info = _C_NONE; break; + case _C_CHR: info = _C_CHR; break; + case _C_UCHR: info = _C_UCHR; break; + case _C_SHT: info = _C_SHT; break; + case _C_USHT: info = _C_USHT; break; + case _C_INT: info = _C_INT; break; + case _C_UINT: info = _C_UINT; break; + case _C_LNG: info = _C_LNG; break; + case _C_ULNG: info = _C_ULNG; break; +#ifdef _C_LNG_LNG + case _C_LNG_LNG: info = _C_LNG_LNG; break; + case _C_ULNG_LNG: info = _C_ULNG_LNG; break; +#endif + case _C_FLT: info = _C_FLT; break; + case _C_DBL: info = _C_DBL; break; + default: info = _C_NONE; break; + } + + if (info == _C_NONE) + { + for (i = 0; i < count; i++) + { + (*dValImp)(self, dValSel, type, (char*)buf + offset); + offset += size; + } + } + else + { + uchar ainfo; + + ainfo = (*tagImp)(src, tagSel, &cursor); + if (info != ainfo) + { + [NSException raise: NSInternalInconsistencyException + format: @"expected %s and got %s", + typeToName(info), typeToName(ainfo)]; + } + for (i = 0; i < count; i++) + { + (*desImp)(src, desSel, (char*)buf + offset, type, &cursor, nil); + offset += size; + } + } +} + +- (void) decodeValueOfObjCType: (const char*)type + at: (void*)address +{ + uchar info = (*tagImp)(src, tagSel, &cursor); + + switch (info & _C_MASK) + { + case _C_ID: + { + unsigned xref; + id obj; + + typeCheck(*type, _C_ID); + xref = (*xRefImp)(src, xRefSel, &cursor); + /* + * Special case - a zero crossref value is a nil pointer. + */ + if (xref == 0) + { + obj = nil; + } + else + { + if (info & _C_XREF) + { + if (xref >= objMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"object crossref missing - %d", + xref]; + } + obj = (id)objMap->ptr[xref]; + /* + * If it's a cross-reference, we need to retain it in + * order to give the appearance that it's actually a + * new object. + */ + [obj retain]; + } + else + { + Class c; + id rep; + + if (xref != objMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"extra object crossref - %d", + xref]; + } + (*dValImp)(self, dValSel, @encode(Class), &c); + + obj = [c allocWithZone: zone]; + arrayAddItem(objMap, (void*)obj); + + rep = [obj initWithCoder: self]; + if (rep != obj) + { + obj = rep; + objMap->ptr[xref] = (void*)obj; + } + + rep = [obj awakeAfterUsingCoder: self]; + if (rep != obj) + { + obj = rep; + objMap->ptr[xref] = (void*)obj; + } + } + } + *(id*)address = obj; + return; + } + case _C_CLASS: + { + unsigned xref; + Class c; + _GSObjInfo *classInfo; + Class dummy; + + typeCheck(*type, _C_CLASS); + xref = (*xRefImp)(src, xRefSel, &cursor); + if (xref == 0) + { + /* + * Special case - an xref of zero is a nul pointer. + */ + *(SEL*)address = 0; + return; + } + if (info & _C_XREF) + { + if (xref >= clsMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"class crossref missing - %d", xref]; + } + classInfo = (_GSObjInfo*)clsMap->ptr[xref]; + *(Class*)address = [classInfo classObject]; + return; + } + while (info == _C_CLASS) + { + unsigned cver; + NSString *className; + + if (xref != clsMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"extra class crossref - %d", xref]; + } + (*desImp)(src, desSel, &c, @encode(Class), &cursor, nil); + (*desImp)(src, desSel, &cver, @encode(unsigned), &cursor, nil); + if (c == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"decoded nil class"]; + } + className = [NSString stringWithCString: fastClassName(c)]; + classInfo = [objDict objectForKey: className]; + if (classInfo == nil) + { + classInfo = [_GSObjInfo newWithName: className]; + [classInfo mapToClass: c withName: className]; + [objDict setObject: classInfo forKey: className]; + [classInfo release]; + } + [classInfo setVersion: cver]; + arrayAddItem(clsMap, (void*)classInfo); + *(Class*)address = [classInfo classObject]; + /* + * Point the address to a dummy location and read the + * next tag - if it is another class, loop to get it. + */ + address = &dummy; + info = (*tagImp)(src, tagSel, &cursor); + if (info == _C_CLASS) + { + xref = (*xRefImp)(src, xRefSel, &cursor); + } + } + if (info != _C_NONE) + { + [NSException raise: NSInternalInconsistencyException + format: @"class list improperly terminated"]; + } + return; + } + case _C_SEL: + { + unsigned xref; + SEL sel; + + typeCheck(*type, _C_SEL); + xref = (*xRefImp)(src, xRefSel, &cursor); + if (xref == 0) + { + /* + * Special case - an xref of zero is a nul pointer. + */ + *(SEL*)address = 0; + return; + } + if (info & _C_XREF) + { + if (xref >= ptrMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"sel crossref missing - %d", xref]; + } + sel = (SEL)ptrMap->ptr[xref]; + } + else + { + if (xref != ptrMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"extra sel crossref - %d", xref]; + } + (*desImp)(src, desSel, &sel, @encode(SEL), &cursor, nil); + arrayAddItem(ptrMap, (void*)sel); + } + *(SEL*)address = sel; + return; + } + case _C_ARY_B: + { + int count; + + typeCheck(*type, _C_ARY_B); + count = atoi(++type); + while (isdigit(*type)) + { + type++; + } + [self decodeArrayOfObjCType: type count: count at: address]; + return; + } + case _C_STRUCT_B: + { + int offset = 0; + + typeCheck(*type, _C_STRUCT_B); + while (*type != _C_STRUCT_E && *type != '=') + { + type++; + } + for (;;) + { + (*dValImp)(self, dValSel, type, (char*)address + offset); + offset += objc_sizeof_type(type); + type = objc_skip_typespec(type); + if (*type == _C_STRUCT_E) + { + break; + } + else + { + int align = objc_alignof_type(type); + int rem = offset % align; + + if (rem != 0) + { + offset += align - rem; + } + } + } + return; + } + case _C_PTR: + { + unsigned xref; + + typeCheck(*type, _C_PTR); + xref = (*xRefImp)(src, xRefSel, &cursor); + if (xref == 0) + { + /* + * Special case - an xref of zero is a nul pointer. + */ + *(void**)address = 0; + return; + } + if (info & _C_XREF) + { + if (xref >= ptrMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"ptr crossref missing - %d", xref]; + } + *(void**)address = ptrMap->ptr[xref]; + } + else + { + unsigned size; + NSData *dat; + + if (ptrMap->count != xref) + { + [NSException raise: NSInternalInconsistencyException + format: @"extra ptr crossref - %d", xref]; + } + + /* + * Allocate memory for object to be decoded into. + */ + size = objc_sizeof_type(++type); + *(void**)address = NSZoneMalloc(zone, size); + + /* + * Decode value and add memory to map for crossrefs. + */ + (*dValImp)(self, dValSel, type, *(void**)address); + arrayAddItem(ptrMap, *(void**)address); + + /* + * Allocate, initialise, and autorelease an NSData + * object to look after the memory. + */ + dat = (*aDatImp)(_fastCls._NSDataMalloc, aDatSel, zone); + dat = (*iDatImp)(dat, iDatSel, *(void**)address, size, zone); + (*rDatImp)(dat, rDatSel); + } + return; + } + case _C_CHARPTR: + { + unsigned xref; + char *str; + + typeCheck(*type, _C_CHARPTR); + xref = (*xRefImp)(src, xRefSel, &cursor); + if (xref == 0) + { + /* + * Special case - an xref of zero is a nul pointer. + */ + *(char**)address = 0; + return; + } + if (info & _C_XREF) + { + if (xref >= ptrMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"string crossref missing - %d", xref]; + } + *(char**)address = (char*)ptrMap->ptr[xref]; + } + else + { + int length; + + if (xref != ptrMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"extra string crossref - %d", xref]; + } + (*desImp)(src, desSel, address, @encode(char*), &cursor, nil); + arrayAddItem(ptrMap, *(void**)address); + } + return; + } + case _C_CHR: + typeCheck(*type, _C_CHR); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_UCHR: + typeCheck(*type, _C_UCHR); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_SHT: + typeCheck(*type, _C_SHT); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_USHT: + typeCheck(*type, _C_USHT); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_INT: + typeCheck(*type, _C_INT); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_UINT: + typeCheck(*type, _C_UINT); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_LNG: + typeCheck(*type, _C_LNG); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_ULNG: + typeCheck(*type, _C_ULNG); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; +#ifdef _C_LNG_LNG + case _C_LNG_LNG: + typeCheck(*type, _C_LNG_LNG); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_ULNG_LNG: + typeCheck(*type, _C_ULNG_LNG); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; +#endif + case _C_FLT: + typeCheck(*type, _C_FLT); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + case _C_DBL: + typeCheck(*type, _C_DBL); + (*desImp)(src, desSel, address, type, &cursor, nil); + break; + default: + [NSException raise: NSInternalInconsistencyException + format: @"read unknown type info - %d", info]; + } +} + +/* + * The [-decodeObject] method is implemented purely for performance - + * It duplicates the code for handling objects in the + * [-decodeValueOfObjCType:at:] method above, but differs in that the + * resulting object is autoreleased when it comes from this method. + */ +- (id) decodeObject +{ + uchar info; + unsigned xref; + id obj; + + info = (*tagImp)(src, tagSel, &cursor); + if ((info & _C_MASK) != _C_ID) + { + [NSException raise: NSInternalInconsistencyException + format: @"expected object and got %s", typeToName(info)]; + } + + xref = (*xRefImp)(src, xRefSel, &cursor); + /* + * Special case - a zero crossref value is a nil pointer. + */ + if (xref == 0) + { + return nil; + } + + if (info & _C_XREF) + { + if (xref >= objMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"object crossref missing - %d", + xref]; + } + obj = (id)objMap->ptr[xref]; + /* + * If it's a cross-reference, we don't need to autorelease it + * since we don't own it. + */ + return obj; + } + else + { + Class c; + id rep; + + if (xref != objMap->count) + { + [NSException raise: NSInternalInconsistencyException + format: @"extra object crossref - %d", + xref]; + } + (*dValImp)(self, dValSel, @encode(Class), &c); + + obj = [c allocWithZone: zone]; + arrayAddItem(objMap, (void*)obj); + + rep = [obj initWithCoder: self]; + if (rep != obj) + { + obj = rep; + objMap->ptr[xref] = (void*)obj; + } + + rep = [obj awakeAfterUsingCoder: self]; + if (rep != obj) + { + obj = rep; + objMap->ptr[xref] = (void*)obj; + } + /* + * A newly allocated object needs to be autoreleased. + */ + return [obj autorelease]; + } +} + +- (BOOL) isAtEnd +{ + return (cursor >= [data length]); +} + +- (NSZone*) objectZone +{ + return zone; +} + +- (void) setObjectZone: (NSZone*)aZone +{ + zone = aZone; +} + +- (unsigned) systemVersion +{ + return version; +} + ++ (NSString*) classNameDecodedForArchiveClassName: (NSString*)nameInArchive +{ + _GSClsInfo *info = [clsDict objectForKey: nameInArchive]; + NSString *alias = [info className]; + + if (alias) + { + return alias; + } + return nameInArchive; +} + ++ (void) decodeClassName: (NSString*)nameInArchive + asClassName: (NSString*)trueName +{ + Class c; + + c = objc_get_class([trueName cString]); + if (c == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"can't find class %@", trueName]; + } + else + { + _GSClsInfo *info = [clsDict objectForKey: nameInArchive]; + + if (info == nil) + { + info = [_GSClsInfo newWithName: nameInArchive]; + [clsDict setObject: info forKey: nameInArchive]; + [info release]; + } + [info mapToClass: c withName: trueName]; + } +} + +- (NSString*) classNameDecodedForArchiveClassName: (NSString*)nameInArchive +{ + _GSObjInfo *info = [objDict objectForKey: nameInArchive]; + NSString *alias = [info className]; + + if (alias) + { + return alias; + } + return nameInArchive; +} + + +- (void) decodeClassName: (NSString*)nameInArchive + asClassName: (NSString*)trueName +{ + Class c; + + c = objc_get_class([trueName cString]); + if (c == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"can't find class %@", trueName]; + } + else + { + _GSObjInfo *info = [objDict objectForKey: nameInArchive]; + + if (info == nil) + { + info = [_GSObjInfo newWithName: nameInArchive]; + [objDict setObject: info forKey: nameInArchive]; + [info release]; + } + [info mapToClass: c withName: trueName]; + } +} + +- (void) replaceObject: (id)anObject withObject: (id)replacement +{ + unsigned i; + + for (i = objMap->count - 1; i > 0; i--) + { + if (objMap->ptr[i] == (void*)anObject) + { + objMap->ptr[i] = (void*)replacement; + return; + } + } + [NSException raise: NSInvalidArgumentException + format: @"object to be replaced does not exist"]; +} + +- (unsigned) versionForClassName: (NSString*)className +{ + return [[objDict objectForKey: className] version]; +} + +@end + + + + +@implementation NSUnarchiver (GNUstep) + +/* Re-using the unarchiver */ + +- (unsigned) cursor +{ + return cursor; +} + +- (void) resetUnarchiverWithData: (NSData*)anObject + atIndex: (unsigned)pos +{ + unsigned sizeC; + unsigned sizeO; + unsigned sizeP; + + if (anObject == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"nil passed to resetUnarchiverWithData:atIndex:"]; + } + if (data != anObject) + { + Class c; + + [data release]; + data = [anObject retain]; + c = fastClass(data); + if (src != self) + { + src = data; + if (c != dataClass) + { + /* + * Cache methods for deserialising from the data object. + */ + desImp = [src methodForSelector: desSel]; + tagImp = (unsigned char (*)(id, SEL, unsigned*)) + [src methodForSelector: tagSel]; + xRefImp = (unsigned (*)(id, SEL, unsigned*)) + [src methodForSelector: xRefSel]; + } + } + dataClass = c; + } + + /* + * Read header including version and crossref table sizes. + */ + cursor = pos; + [self deserializeHeaderAt: &cursor + version: &version + classes: &sizeC + objects: &sizeO + pointers: &sizeP]; + + if (clsMap == 0) + { + /* + * Allocate and initialise arrays to build crossref maps in. + */ + clsMap = NSZoneMalloc(zone, sizeof(GSUnarchiverArray_t)*3); + clsMap->zone = zone; + clsMap->count = 1; + clsMap->cap = sizeC; + clsMap->old = sizeC/2 ? sizeC/2 : 1; + clsMap->ptr = (void**)NSZoneMalloc(zone, sizeof(void*)*clsMap->cap); + + objMap = &clsMap[1]; + objMap->zone = zone; + objMap->count = 1; + objMap->cap = sizeO; + objMap->old = sizeO/2 ? sizeO/2 : 1; + objMap->ptr = (void**)NSZoneMalloc(zone, sizeof(void*)*objMap->cap); + + ptrMap = &clsMap[2]; + ptrMap->zone = zone; + ptrMap->count = 1; + ptrMap->cap = sizeP; + ptrMap->old = sizeP/2 ? sizeP/2 : 1; + ptrMap->ptr = (void**)NSZoneMalloc(zone, sizeof(void*)*ptrMap->cap); + } + else + { + clsMap->count = 1; + objMap->count = 1; + ptrMap->count = 1; + } + + [objDict removeAllObjects]; +} + +- (void) deserializeHeaderAt: (unsigned*)pos + version: (unsigned*)v + classes: (unsigned*)c + objects: (unsigned*)o + pointers: (unsigned*)p +{ + unsigned plen = strlen(PREFIX); + unsigned size = plen+36; + char header[size+1]; + + [data getBytes: header range: NSMakeRange(*pos, size)]; + *pos += size; + header[size] = '\0'; + if (strncmp(header, PREFIX, plen) != 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"Archive has wrong prefix"]; + } + if (sscanf(&header[plen], "%x:%x:%x:%x:", v, c, o, p) != 4) + { + [NSException raise: NSInternalInconsistencyException + format: @"Archive has wrong prefix"]; + } +} + +- (BOOL) directDataAccess +{ + return YES; +} + +/* libObjects compatibility */ + +- (void) decodeArrayOfObjCType: (const char*) type + count: (unsigned)count + at: (void*)buf + withName: (id*)name +{ + if (name) + { + (*dValImp)(self, dValSel, @encode(id), (void*)name); + } + else + { + id obj; + (*dValImp)(self, dValSel, @encode(id), (void*)&obj); + if (obj) + { + [obj release]; + } + } + [self decodeArrayOfObjCType: type count: count at: buf]; +} + +- (void) decodeIndent +{ +} + +- (void) decodeValueOfCType: (const char*) type + at: (void*)buf + withName: (id*)name +{ + if (name) + { + (*dValImp)(self, dValSel, @encode(id), (void*)name); + } + else + { + id obj; + (*dValImp)(self, dValSel, @encode(id), (void*)&obj); + if (obj) + { + [obj release]; + } + } + (*dValImp)(self, dValSel, type, buf); +} + +- (void) decodeValueOfObjCType: (const char*) type + at: (void*)buf + withName: (id*)name +{ + if (name) + { + (*dValImp)(self, dValSel, @encode(id), (void*)name); + } + else + { + id obj; + (*dValImp)(self, dValSel, @encode(id), (void*)&obj); + if (obj) + { + [obj release]; + } + } + (*dValImp)(self, dValSel, type, buf); +} + +- (void) decodeObjectAt: (id*)anObject + withName: (id*)name +{ + if (name) + { + (*dValImp)(self, dValSel, @encode(id), (void*)name); + } + else + { + id obj; + (*dValImp)(self, dValSel, @encode(id), (void*)&obj); + if (obj) + { + [obj release]; + } + } + (*dValImp)(self, dValSel, @encode(id), (void*)anObject); +} +@end +