/* 
   NSArchiver.m

   Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>

   This file is part of libFoundation.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

#include <config.h>
#include <ctype.h>

#include <Foundation/common.h>
#include <Foundation/NSArchiver.h>
#include <Foundation/NSString.h>
#include <Foundation/NSException.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSData.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/exceptions/GeneralExceptions.h>
#include <Foundation/exceptions/NSCoderExceptions.h>

#include <extensions/objc-runtime.h>

extern id nil_method(id, SEL, ...);

typedef enum {
    OP_NONE = 0, OP_ID,		OP_CLASS,	OP_SEL,		OP_STRING,
    OP_CHAR,	OP_SHORT,	OP_INT,		OP_LONG,	OP_FLOAT,
    OP_DOUBLE,	OP_ARRAY,	OP_STRUCT,	OP_PTR,		OP_LAST
} TValueType;

typedef enum {
    REFERENCE = 0x20
} TReferenceInfo;

enum {
    VALUE_TYPE_MASK = 0x1f
};

#define SIGNATURE	"libFoundation NSArchiver"


@interface ArchiverClassInfo : NSObject
{
    NSString*	className;
    unsigned	version;
    BOOL	written;
}
- setClassName:(NSString*)name;
- className;
- setVersion:(int)version;
- (unsigned)version;
- setWritten:(BOOL)flag;
- (BOOL)written;
@end

@implementation ArchiverClassInfo

- (void)dealloc
{
    [className release];
    [super dealloc];
}

- setClassName:(NSString*)name
{
    [name retain];
    [className release];
    className = name;
    return self;
}

- setVersion:(int)_version
{
    version = _version;
    return self;
}

- setWritten:(BOOL)flag
{
    written = flag;
    return self;
}

- (void)encodeWithCoder:(NSCoder*)coder
{
    const char* classNameAsString = [className cString];

    [coder encodeValuesOfObjCTypes:"*I", &classNameAsString, &version];
}

- (id)initWithCoder:(NSCoder*)coder
{
    char* classNameAsString;

    [coder decodeValuesOfObjCTypes:"*I", &classNameAsString, &version];
    className = [[NSString stringWithCString:classNameAsString] retain];
    Free(classNameAsString);

    return self;
}

- className		{ return className; }
- (unsigned)version	{ return version; }
- (BOOL)written		{ return written; }

@end


@implementation NSArchiver

- init
{
    [self initForWritingWithMutableData:[NSMutableData new]];
    return [super init];
}

- (id)initForWritingWithMutableData:(NSMutableData*)_mdata
{
    const char* signature = SIGNATURE;
    unsigned version = [isa version];

    [_mdata retain];
    [mdata release];
    mdata = _mdata;
    writeIMP = [mdata methodForSelector:
			@selector(serializeDataAt:ofObjCType:context:)];

    if(!objects)
	objects = NSCreateHashTable (NSNonOwnedPointerHashCallBacks, 119);
    else
	NSResetHashTable (objects);

    if(!conditionals)
	conditionals = NSCreateHashTable (NSNonOwnedPointerHashCallBacks, 19);
    else
	NSResetHashTable (conditionals);

    if(!classes)
	classes = NSCreateMapTable (NSObjectMapKeyCallBacks,
				    NSObjectMapValueCallBacks, 19);
    else
	NSResetMapTable (classes);

    if(!pointers)
	pointers = NSCreateHashTable (NSNonOwnedPointerHashCallBacks, 0);
    else
	NSResetHashTable (pointers);

    [mdata serializeDataAt:&signature ofObjCType:@encode(char*) context:nil];
    [mdata serializeDataAt:&version ofObjCType:@encode(int) context:nil];
    return self;
}

- (void)dealloc
{
    [mdata release];
    NSFreeHashTable(objects);
    NSFreeHashTable(conditionals);
    NSFreeMapTable(classes);
    NSFreeHashTable(pointers);

    return [super dealloc];
}

+ (NSData*)archivedDataWithRootObject:(id)rootObject
{
    NSArchiver* archiver = [[self new] autorelease];
    id data;

    [archiver encodeRootObject:rootObject];
    data = [[archiver->mdata copy] autorelease];
    return data;
}

+ (BOOL)archiveRootObject:(id)rootObject
  toFile:(NSString*)path
{
    id data = [self archivedDataWithRootObject:rootObject];
    return [data writeToFile:path atomically:YES];
}

- (void)encodeArrayOfObjCType:(const char*)type
  count:(unsigned int)count
  at:(const void*)array
{
    int i, offset, item_size = objc_sizeof_type(type);
    char tag = OP_ARRAY;
    SEL writeSel = @selector(serializeDataAt:ofObjCType:context:);

    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
    (*writeIMP) (mdata, writeSel, &count, @encode(int), nil);

    /* Optimize writing arrays of elementary types. If such an array has to
	be written, write the type and then the elements of array. */
    switch(*type) {
	case _C_ID:	tag = _C_ID; break;
	case _C_CHR:
	case _C_UCHR:   tag = OP_CHAR; break;
	case _C_SHT:
	case _C_USHT:   tag = OP_SHORT; break;
	case _C_INT:
	case _C_UINT:   tag = OP_INT; break;
	case _C_LNG:
	case _C_ULNG:   tag = OP_LONG; break;
	case _C_FLT:    tag = OP_FLOAT; break;
	case _C_DBL:    tag = OP_DOUBLE; break;
	default:	tag = OP_NONE; break;
    }
    if(tag == OP_NONE) {
	SEL selector = @selector(encodeValueOfObjCType:at:);
	IMP imp = [self methodForSelector:selector];

	for(i = offset = 0; i < count; i++, offset += item_size)
	    (*imp) (self, selector, type, (char*)array + offset);
    }
    else {
	(*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	for(i = offset = 0; i < count; i++, offset += item_size)
	    (*writeIMP) (mdata, writeSel, (char*)array + offset, type, nil);
    }
}

- (void)encodeValueOfObjCType:(const char*)type
  at:(const void*)data
{
    char tag;
    SEL writeSel = @selector(serializeDataAt:ofObjCType:context:);

    switch(*type) {
	case _C_ID: {
	/*  Write another tag just to be possible to read using the
	    decodeObject method. */
	    tag = OP_ID;
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);

	    [self encodeObject:*(void**)data];
	    return;
	}
    case _C_CLASS:	{
	id className = NSStringFromClass(*(Class*)data);
	id classInfo = NSMapGet(classes, className);

	tag = OP_CLASS;

	if(classInfo && [classInfo written]) {
	    tag |= REFERENCE;
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);
	}
	else {
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);

	    /* The classInfo could be nil if the encodeClassName:intoClassName:
	       was not previously sent for this class name. Create a new entry
	       in classes that has as key this class. */
	    if(!classInfo) {
		classInfo = [[(ArchiverClassInfo*)[ArchiverClassInfo alloc]
				setClassName:className]
				setVersion:[*(Class*)data version]];
		NSMapInsert(classes, className, classInfo);
		[classInfo release];
	    }

	    [classInfo encodeWithCoder:self];
	    [classInfo setWritten:YES];
	}
	return;
    }
    case _C_SEL: {
	tag = OP_SEL;
	if (NSHashGet (pointers, *(SEL*)data)) {
	    /* The selector was previously written */
	    tag |= REFERENCE;
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);
	}
	else {
	    /* The selector was not yet written */
	    const char* selName = [NSStringFromSelector(*(SEL*)data) cString];

	    NSHashInsert (pointers, *(SEL*)data);
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);
	    (*writeIMP) (mdata, writeSel, &selName, @encode(char*), nil);
	}
	    
	return;
    }
    case _C_ARY_B: {
	int count = Atoi(type + 1);
	const char* itemType = type;

	tag = OP_ARRAY;
	while(isdigit(*++itemType))
	    /* nothing */;

	/*  Write another tag just to be possible to read using the
	    decodeArrayOfObjCType:count:at: method. */
	(*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);

	[self encodeArrayOfObjCType:itemType count:count at:data];
	return;
    }
    case _C_STRUCT_B: {
	int offset = 0;
	int align, rem;

	tag = OP_STRUCT;
	(*writeIMP)(mdata, writeSel, &tag, @encode(char), nil);

	while(*type != _C_STRUCT_E && *type++ != '=')
	    /* skip "<name>=" */;
	while(1) {
	    [self encodeValueOfObjCType:type at:((char*)data) + offset];
	    offset += objc_sizeof_type(type);
	    type = objc_skip_typespec(type);
	    if(*type != _C_STRUCT_E) {
		align = objc_alignof_type(type);
		if((rem = offset % align))
		    offset += align - rem;
	    }
	    else break;
	}
	return;
    }
    case _C_PTR: {
	tag = OP_PTR;
	if (NSHashGet (pointers, *(char**)data)) {
	    tag |= REFERENCE;
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);	    
	}
	else {
	    NSHashInsert (pointers, *(char**)data);
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);	    
	    type++; data = *(char**)data;
	    [self encodeValueOfObjCType:type at:data];
	}
	return;
    }
    case _C_CHARPTR: {
	const char* cStr = *(void**)data;
	unsigned value = cStr ? (unsigned)NSHashGet (pointers, cStr) : 0;

	tag = OP_STRING;
	if(value) {
	    tag |= REFERENCE;
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, data, @encode(unsigned), nil);	    
	}
	else {
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    value = (unsigned)cStr;
	    (*writeIMP) (mdata, writeSel, &value, @encode(unsigned), nil);
	    if(cStr) {
		int len = Strlen(cStr);

		NSHashInsert (pointers, cStr);
		(*writeIMP) (mdata, writeSel, &len, @encode(unsigned), nil);
		[mdata appendBytes:cStr length:len];
	    }
	}
	return;
    }

#define WRITE_TYPE_TAG(TYPE, TAG) \
	tag = TAG; \
	(*writeIMP) (mdata, writeSel, &tag, @encode(char), nil); \
	(*writeIMP) (mdata, writeSel, data, @encode(TYPE), nil);

    case _C_CHR:
    case _C_UCHR: {
	WRITE_TYPE_TAG (char, OP_CHAR)
	break;
    }
    case _C_SHT:
    case _C_USHT: {
	WRITE_TYPE_TAG (short, OP_SHORT)
	break;
    }
    case _C_INT:
    case _C_UINT: {
	WRITE_TYPE_TAG (int, OP_INT)
	break;
    }
    case _C_LNG:
    case _C_ULNG: {
	WRITE_TYPE_TAG (long, OP_LONG)
	break;
    }
    case _C_FLT: {
	WRITE_TYPE_TAG (float, OP_FLOAT)
	break;
    }
    case _C_DBL: {
	WRITE_TYPE_TAG (double, OP_DOUBLE)
	break;
    }

#undef WRITE_TYPE_TAG

    case _C_VOID:
	THROW([[InvalidArgumentException alloc]
		    initWithReason:@"cannot encode void type"]);
    default:
	THROW([[InvalidArgumentException alloc]
		    initWithFormat:@"unknown type %s", type]);
    }
}

- (void)encodeRootObject:(id)rootObject
{
    id originalData = mdata;
    IMP originalWriteIMP = writeIMP;

    if(writingRoot) {
	THROW([CoderHasAlreadyWrittenRootObjectException new]);
	return;
    }

    writingRoot = YES;

    /*
     * Prepare for writing the graph objects for which `rootObject' is the root
     * node. The algorithm consists from two passes. In the first pass it
     * determines the nodes so-called 'conditionals' - the nodes encoded *only*
     * with -encodeConditionalObject:. They represent nodes that are not
     * related directly to the graph. In the second pass objects are encoded
     * normally, except for the conditional objects which are encoded as nil.
     */

    /* First pass. */
    findingConditionals = YES;
    mdata = nil;
    writeIMP = nil_method;
    NSResetHashTable(conditionals);
    NSResetHashTable(objects);
    [self encodeObject:rootObject];

    /* Second pass. */
    findingConditionals = NO;
    mdata = originalData;
    writeIMP = originalWriteIMP;
    NSResetHashTable(objects);
    [self encodeObject:rootObject];

    writingRoot = NO;
}

- (void)encodeConditionalObject:(id)anObject
{
    if(!writingRoot) {
	THROW([RootObjectHasNotBeenWrittenException new]);
	return;
    }

    if(findingConditionals) {
	/*
	 * This is the first pass of the determining the conditionals
	 * algorithm. We traverse the graph and insert into the `conditionals'
	 * set. In the second pass all objects that are still in this set will
	 * be encoded as nil when they receive -encodeConditionalObject:. An
	 * object is removed from this set when it receives -encodeObject:.
	 */
	void* value;

	if(!anObject)
	    return;

	/* Lookup anObject into the `conditionals' set. */
	value =  NSHashGet(conditionals, anObject);
	if(value)
	    return;

	/*
	 * Maybe it has received -encodeObject:
	 * and now is in the `objects' set.
	 */
	value = NSHashGet(objects, anObject);
	if(value)
	    return;

	/* anObject was not written previously. */
	NSHashInsert(conditionals, anObject);
    }
    else {
	/* If anObject is in the `conditionals' set, it is encoded as nil. */
	if(!anObject || NSHashGet(conditionals, anObject))
	    [self encodeObject:nil];
	else [self encodeObject:anObject];
    }
}

- (void)encodeObject:(id)anObject
{
    char tag = OP_ID;
    void* value = 0;
    SEL writeSel = @selector(serializeDataAt:ofObjCType:context:);

    if(!anObject) {
	tag |= REFERENCE;
	(*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	(*writeIMP) (mdata, writeSel, &value, @encode(unsigned), nil);
    }
    else {
	value = NSHashGet(objects, anObject);

	if(findingConditionals && !value) {
	    /*
	     * Look-up the object in the `conditionals' set. If the object is
	     * there, then remove it because it is no longer a conditional one.
	     */
	    value = NSHashGet(conditionals, anObject);
	    if(value) {
		NSHashRemove(conditionals, anObject);
		NSHashInsert(objects, anObject);
		return;
	    }
	}

	if(!value) {
	    Class archiveClass;

	    value = anObject;
	    anObject = [anObject replacementObjectForCoder:self];
	    if(!findingConditionals)
		archiveClass = [anObject classForCoder];

	    NSHashInsert(objects, anObject);
	    if(!findingConditionals) {
		(*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
		(*writeIMP) (mdata, writeSel, &value, @encode(unsigned), nil);
		[self encodeValueOfObjCType:"#" at:&archiveClass];
	    }
	    else {
		/*
		 * This is the first pass of determining the conditionals
		 * objects algorithm. Remove anObject from the `conditionals'
		 * set if it is there and insert it into the `objects' set.
		 */
		NSHashRemove(conditionals, anObject);
	    }
	    [anObject encodeWithCoder:self];
	}
	else if(!findingConditionals) {
	    tag |= REFERENCE;
	    (*writeIMP) (mdata, writeSel, &tag, @encode(char), nil);
	    (*writeIMP) (mdata, writeSel, &value, @encode(unsigned), nil);
	}
    }
}

- (NSMutableData*)archiverData
{
    return mdata;
}

- (NSString*)classNameEncodedForTrueClassName:(NSString*)trueName
{
    id inArchiveName = [(id)NSMapGet(classes, trueName) className];

    return inArchiveName ? inArchiveName : trueName;
}

/* In the following method the version of class named trueName is written as
   version for class named inArchiveName. Is this right? The inArchiveName
   class could not be linked in the running process at the time the archive
   is written. */
- (void)encodeClassName:(NSString*)trueName
  intoClassName:(NSString*)inArchiveName
{
    id classInfo = [[(ArchiverClassInfo*)[ArchiverClassInfo alloc]
			setClassName:inArchiveName]
			setVersion:[NSClassFromString(trueName) version]];
    NSMapInsert(classes, trueName, classInfo);
    [classInfo release];
}

@end /* NSArchiver */


@implementation NSUnarchiver

static NSMapTable* classToAliasMappings;	// archive name -> decoded name

+ (void)initialize
{
    classToAliasMappings = NSCreateMapTable(NSObjectMapKeyCallBacks,
					    NSObjectMapValueCallBacks, 19);
}

- (id)initForReadingWithData:(NSData*)data
{
    int siglen = strlen(SIGNATURE);
    char *signature;
    SEL readSel = @selector(deserializeDataAt:ofObjCType:atCursor:context:);

    if(!data)
	THROW([[InvalidArgumentException alloc]
		initWithReason:@"argument of -initForReadingWithData: "
				@"has to be non-nil"]);

    if (!objects)
	objects = NSCreateMapTable (NSIntMapKeyCallBacks,
				    NSObjectMapValueCallBacks, 119);
    else
	NSResetMapTable (objects);
    if (!classes)
	classes = NSCreateMapTable (NSIntMapKeyCallBacks,
				    NSObjectMapValueCallBacks, 19);
    else
	NSResetMapTable (classes);
    if (!pointers)
	pointers = NSCreateMapTable (NSIntMapKeyCallBacks,
				    NSIntMapValueCallBacks, 19);
    else
	NSResetMapTable (pointers);
    if (!classAlias)
	classAlias = NSCreateMapTable (NSObjectMapKeyCallBacks,
					NSObjectMapValueCallBacks, 19);
    else
	NSResetMapTable (classAlias);
    if (!classVersions)
	classVersions = NSCreateMapTable (NSObjectMapKeyCallBacks,
					    NSObjectMapValueCallBacks, 19);
    else
	NSResetMapTable (classVersions);

    [data retain];
    [rdata release];
    rdata = data;
    cursor = 0;
    readIMP = [rdata methodForSelector:readSel];

    (*readIMP) (rdata, readSel, &signature, @encode(char*), &cursor, nil);
    if (Strncmp (signature, SIGNATURE, siglen))
	THROW([InvalidSignatureForCoderException new]);

    Free (signature);
    archiverVersion = [rdata deserializeIntAtCursor:&cursor];

    return self;
}

+ (id)unarchiveObjectWithData:(NSData*)data
{
    NSUnarchiver* unarchiver = [[self alloc] initForReadingWithData:data];
    id object = [unarchiver decodeObject];

    [unarchiver release];
    return object;
}

- (void)dealloc
{
    [rdata release];
    NSFreeMapTable(objects);
    NSFreeMapTable(classes);
    NSFreeMapTable(pointers);
    NSFreeMapTable(classAlias);
    NSFreeMapTable(classVersions);

    return [super dealloc];
}

+ (id)unarchiveObjectWithFile:(NSString*)path
{
    NSData* data = [NSData dataWithContentsOfFile:path];

    if (!data)
	return nil;
    return [self unarchiveObjectWithData:data];
}

static NSString* make_type(char tag)
{
    NSString* type[OP_LAST] = {
	/* OP_NONE,  OP_ID,	OP_CLASS, OP_SEL,    OP_STRING */
	    @"",	@"@",	@"#",	   @":",	@"*",

	/* OP_CHAR,  OP_SHORT,  OP_INT */
	    @"c",	@"s",	@"i",

	/* OP_LONG,  OP_FLOAT,  OP_DOUBLE,*/
	    @"l",	@"f",	@"d",

	/* OP_ARRAY, OP_STRUCT,	OP_PTR, */
	    @"[",	@"{",	@"^"
    };

    NSCAssert1 (tag > OP_NONE && tag < OP_LAST, @"unknown tag %d", tag);
    return type[(int)tag];
}

- (void)decodeArrayOfObjCType:(const char*)type
  count:(unsigned int)count
  at:(void*)array
{
    int i, offset, item_size = objc_sizeof_type(type);
    char tag, written_tag;
    int written_count;
    SEL readSel = @selector(deserializeDataAt:ofObjCType:atCursor:context:);

    (*readIMP) (rdata, readSel, &tag, @encode(char), &cursor, nil);
    (*readIMP) (rdata, readSel, &written_count, @encode(int), &cursor, nil);
    if(tag != OP_ARRAY)
	THROW([UnexpectedTypeException allocForExpected:@"["
					andGot:make_type(tag)]);
    if(written_count != count)
	THROW([UnexpectedTypeException allocForExpectedSize:count
					andGotSize:written_count]);

    /* Arrays of elementary types are written optimized: the type is written
       then the elements of array follow. */
    switch(*type) {
	case _C_ID:	tag = _C_ID; break;
	case _C_CHR:
	case _C_UCHR:   tag = OP_CHAR; break;
	case _C_SHT:
	case _C_USHT:   tag = OP_SHORT; break;
	case _C_INT:
	case _C_UINT:   tag = OP_INT; break;
	case _C_LNG:
	case _C_ULNG:   tag = OP_LONG; break;
	case _C_FLT:    tag = OP_FLOAT; break;
	case _C_DBL:    tag = OP_DOUBLE; break;
	default:	tag = OP_NONE; break;
    }
    if(tag == OP_NONE) {
	SEL selector = @selector(decodeValueOfObjCType:at:);
	IMP imp = [self methodForSelector:selector];

	for(i = offset = 0; i < count; i++, offset += item_size)
	    (*imp)(self, selector, type, (char*)array + offset);
    }
    else {
	(*readIMP)(rdata, readSel, &written_tag, @encode(char), &cursor, nil);
	if(tag != written_tag)
	    THROW([UnexpectedTypeException allocForExpected:make_type(tag)
					    andGot:make_type(written_tag)]);
	for(i = offset = 0; i < count; i++, offset += item_size)
	    (*readIMP)(rdata, readSel, ((char*)array) + offset, type,
			&cursor, nil);
    }
}

#define CHECK1(against) \
        if(*type != against) \
            THROW([UnexpectedTypeException \
			allocForExpected:[NSString stringWithCString:type] \
			andGot:make_type(tag & VALUE_TYPE_MASK)]);

#define CHECK2(against1, against2) \
        if(*type != against1 && *type != against2) \
            THROW([UnexpectedTypeException \
			allocForExpected:[NSString stringWithCString:type] \
			andGot:make_type(tag & VALUE_TYPE_MASK)]);

- (void)decodeValueOfObjCType:(const char*)type
  at:(void*)address
{
    char tag;
    SEL readSel = @selector(deserializeDataAt:ofObjCType:atCursor:context:);

    /* This statement, by taking the address of `type', forces the compiler
       to not allocate `type' into a register */
    *(void**)address = &type;

    (*readIMP)(rdata, readSel, &tag, @encode(char), &cursor, nil);
    switch(tag & VALUE_TYPE_MASK) {
	case OP_ID: {
	    CHECK1(_C_ID)
	    *(id*)address = [self decodeObject];
	    return;
	}
	case OP_CLASS: {
	    void* key;
	    NSString* archiveClassName;
	    NSString* decodedClassName;

	    CHECK1(_C_CLASS)
	    (*readIMP) (rdata, readSel, &key, @encode(unsigned), &cursor, nil);
	    if (tag & REFERENCE)
		archiveClassName = [(id)NSMapGet(classes, key) className];
	    else {
		id classInfo = [ArchiverClassInfo new];

		[classInfo initWithCoder:self];
		NSMapInsert(classes, key, classInfo);
		NSMapInsert(classVersions, [classInfo className], classInfo);
		[classInfo release];
		archiveClassName = [classInfo className];
	    }

	    NSAssert (archiveClassName, @"archiveClassName should be non-nil");
	    if(!(decodedClassName = NSMapGet(classAlias, archiveClassName)))
		decodedClassName = archiveClassName;
	    *(Class*)address = NSClassFromString(decodedClassName);
	    if(!*(Class*)address)
                THROW([(UnknownClassException*)[UnknownClassException alloc]
			    setClassName:decodedClassName]);
	    return;
	}
	case OP_SEL: {
	    void* key;
	    id selName;

	    CHECK1(_C_SEL)
	    (*readIMP) (rdata, readSel, &key, @encode(unsigned), &cursor, nil);
	    if (tag & REFERENCE)
		selName = NSMapGet (objects, key);
	    else {
		char* selNameCString;

		(*readIMP) (rdata, readSel, &selNameCString, @encode(char*),
			    &cursor, nil);
		selName = [NSString stringWithCStringNoCopy:selNameCString
				    freeWhenDone:YES];
		/* Insert the selector into the `objects' table */
		NSMapInsert (objects, key, selName);
	    }
	    *(SEL*)address = NSSelectorFromString(selName);
	    return;
	}
	case OP_ARRAY: {
            int count;
            const char* itemType;

	    CHECK1(_C_ARY_B)
            count = Atoi (type + 1);
            itemType = type;
            while (isdigit (*++itemType))
		/* nothing */;
	    [self decodeArrayOfObjCType:itemType count:count at:address];
            return;
	}
	case OP_STRUCT: {
            int offset = 0;
            int align, rem;

	    CHECK1(_C_STRUCT_B)
	    while (*type != _C_STRUCT_E && *type++ != '=')
		/* skip "<name>=" */;
	    while (1) {
                [self decodeValueOfObjCType:type at:((char*)address) + offset];
                offset += objc_sizeof_type(type);
                type = objc_skip_typespec(type);
                if(*type != _C_STRUCT_E) {
                    align = objc_alignof_type(type);
                    if((rem = offset % align))
                        offset += align - rem;
                }
                else break;
	    }
	    return;
	}
	case OP_PTR: {
            void* key;

	    CHECK1(_C_PTR)
	    (*readIMP)(rdata, readSel, &key, @encode(unsigned), &cursor, nil);
	    if (tag & REFERENCE)
		*(void**)address = NSMapGet(pointers, key);
	    else {
		id addr = nil;

		*(void**)address = Malloc(objc_sizeof_type(++type));

		/* Autorelease *data in case of exceptions */
		addr = [NSAutoreleasedPointer
			    autoreleasePointer:*(void**)address];

		NSMapInsert(pointers, key, *(void**)address);
		[self decodeValueOfObjCType:type at:*(void**)address];

		/* Save the *data from destruction */
		[addr retain];
	    }
	    return;
	}
	case OP_STRING: {
	    void* key;

            CHECK1(_C_CHARPTR)
	    (*readIMP)(rdata, readSel, &key, @encode(unsigned), &cursor, nil);
	    if(tag & REFERENCE)
		*(void**)address = NSMapGet(pointers, key);
	    else {
		if(key) {
		    int len;
		    NSRange range;
		    id addr = nil;

		    (*readIMP)(rdata, readSel, &len, @encode(unsigned),
				&cursor, nil);
		    *(char**)address = Malloc(len + 1);

		    /* Autorelease *address in case of exceptions */
		    addr = [NSAutoreleasedPointer
				autoreleasePointer:*(char**)address];

		    NSMapInsert(pointers, key, *(char**)address);
		    range = NSMakeRange(cursor, len);
		    [rdata getBytes:*(char**)address range:range];
		    cursor += len;
		    (*(char**)address)[len] = 0;

		    /* Save the *address from destruction */
		    [addr retain];
		}
		else *(char**)address = NULL;
	    }
	    return;
	}
        case OP_CHAR:   CHECK2(_C_CHR, _C_UCHR) break;
        case OP_SHORT:  CHECK2(_C_SHT, _C_USHT) break;
        case OP_INT:    CHECK2(_C_INT, _C_UINT) break;
        case OP_LONG:   CHECK2(_C_LNG, _C_ULNG) break;
        case OP_FLOAT:  CHECK1(_C_FLT) break;
        case OP_DOUBLE: CHECK1(_C_DBL) break;
        default:
	    THROW([[ReadUnknownTagException alloc] initForTag:tag]);
    }
    (*readIMP)(rdata, readSel, address, type, &cursor, nil);
}

- (id)decodeObject
{
    SEL readSel = @selector(deserializeDataAt:ofObjCType:atCursor:context:);
    char tag;
    void* key;
    id object;

    (*readIMP)(rdata, readSel, &tag, @encode(char), &cursor, nil);
    if((tag & VALUE_TYPE_MASK) != OP_ID)
	THROW([UnexpectedTypeException allocForExpected:@"@"
		    andGot:make_type(tag & VALUE_TYPE_MASK)]);

    (*readIMP)(rdata, readSel, &key, @encode(unsigned), &cursor, nil);

    if(tag & REFERENCE)
	object = key ? NSMapGet(objects, key) : nil;
    else {
	Class class;
	id new_object;

	[self decodeValueOfObjCType:"#" at:&class];
	object = [class allocWithZone:objectZone];
	NSMapInsert(objects, key, object);
	new_object = [object initWithCoder:self];
	if(new_object != object) {
	    object = new_object;
	    NSMapInsert(objects, key, object);
	}
	new_object = [object awakeAfterUsingCoder:self];
	if(new_object != object) {
	    object = new_object;
	    NSMapInsert(objects, key, object);
	}
    }
    return object;
}

- (BOOL)isAtEnd
{
    return (cursor >= [rdata length]);
}

- (NSZone*)objectZone
{
    return objectZone;
}

- (void)setObjectZone:(NSZone*)zone
{
    objectZone = zone;
}

- (unsigned int)systemVersion
{
    return archiverVersion;
}

+ (NSString*)classNameDecodedForArchiveClassName:(NSString*)nameInArchive
{
    NSString* className = NSMapGet(classToAliasMappings, nameInArchive);

    return className ? className : nameInArchive;
}

+ (void)decodeClassName:(NSString*)nameInArchive
  asClassName:(NSString*)trueName
{
    NSMapInsert(classToAliasMappings, nameInArchive, trueName);
}

- (NSString*)classNameDecodedForArchiveClassName:(NSString*)nameInArchive
{
    NSString* className = NSMapGet(classAlias, nameInArchive);

    return className ? className : nameInArchive;
}


- (void)decodeClassName:(NSString*)nameInArchive
  asClassName:(NSString*)trueName
{
    NSMapInsert(classAlias, nameInArchive, trueName);
}

- (unsigned int)versionForClassName:(NSString*)className
{
    return [(id)NSMapGet(classVersions, className) version];
}

@end /* NSUnarchiver */
