// Copyright 1997-1999 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OFBundledClass.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>

#import "NSDictionary-OFExtensions.h"
#import "NSObject-OFExtensions.h"
#import "NSThread-OFExtensions.h"

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniFoundation/OFBundledClass.m,v 1.15 1999/03/30 21:25:29 kc Exp $")

@interface OFBundledClass (Private)

+ (void)addImmediateLoadClass:(OFBundledClass *)aClass;

- initWithClassName:(NSString *)aClassName;

- (void)setBundle:(NSBundle *)aBundle;
- (void)addDependencyClassNamed:(NSString *)aClassName;
- (void)addModifyingBundledClass:(OFBundledClass *)aBundledClass;
- (void)addDependencyClassNames:(NSArray *)anArray;
- (void)modifiesClassesNamed:(NSArray *)anArray;

- (void)loadDependencyClasses;
- (void)loadModifierClasses;

- (void)processDescription:(NSDictionary *)description;

@end

@implementation OFBundledClass;

static NSLock *bundleLock;
static NSMutableDictionary *bundledClassRegistry;
static NSString *OFBundledClassDidLoadNotification;
static NSMutableArray *immediateLoadClasses;

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
        return;
    initialized = YES;

    bundleLock = [[NSRecursiveLock alloc] init];
    bundledClassRegistry = [[NSMutableDictionary alloc] initWithCapacity:64];
    immediateLoadClasses = [[NSMutableArray alloc] init];
    OFBundledClassDidLoadNotification = [@"OFBundledClassDidLoad" retain];
}

static BOOL OFBundledClassDebug = NO;

+ (Class)classNamed:(NSString *)aClassName;
{
    return [[self bundledClassNamed:aClassName] bundledClass];
}

+ (NSBundle *)bundleForClassNamed:(NSString *)aClassName;
{
    return [[self bundledClassNamed:aClassName] bundle];
}

+ (OFBundledClass *)bundledClassNamed:(NSString *)aClassName;
{
    OFBundledClass *bundledClass;

    if (!aClassName || ![aClassName length])
	return nil;

    [bundleLock lock];
    bundledClass = [bundledClassRegistry objectForKey:aClassName];
    if (!bundledClass) {
	bundledClass = [[self alloc] initWithClassName:aClassName];
	if (bundledClass)
	    [bundledClassRegistry setObject:bundledClass forKey:aClassName];
    }
    [bundleLock unlock];
    return bundledClass;
}

+ (void)createBundledClassWithName:(NSString *)aClassName bundle:(NSBundle *)aBundle description:(NSDictionary *)aDescription;
{
    OFBundledClass *bundledClass;
    
    bundledClass = [self bundledClassNamed:aClassName];
    [bundledClass setBundle:aBundle];
    [bundledClass processDescription:aDescription];
}

+ (NSString *)didLoadNotification;
{
    return OFBundledClassDidLoadNotification;
}

+ (void)processImmediateLoadClasses;
{
    while ([immediateLoadClasses count] > 0) {
        unsigned int classIndex, classCount;
        NSArray *immediateLoadClassesCopy;

        immediateLoadClassesCopy = [[NSArray alloc] initWithArray:immediateLoadClasses];
        [immediateLoadClasses removeAllObjects];
        classCount = [immediateLoadClassesCopy count];
        for (classIndex = 0; classIndex < classCount; classIndex++) {
            OFBundledClass *immediateLoadClass;

            immediateLoadClass = [immediateLoadClassesCopy objectAtIndex:classIndex];
            [immediateLoadClass loadBundledClass];
        }
        [immediateLoadClassesCopy release];
    }
}

// OFBundleRegistryTarget informal protocol

+ (void)registerItemName:(NSString *)itemName bundle:(NSBundle *)aBundle description:(NSDictionary *)description;
{
    [self createBundledClassWithName:itemName bundle:aBundle description:description];
}

// Init and dealloc

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

// Access

- (NSString *)className;
{
    return className;
}

- (Class)bundledClass;
{
    if (!bundleClass)
	[self loadBundledClass];
    return bundleClass;
}

- (NSBundle *)bundle;
{
    Class aClass;

    if (bundle)
	return bundle;
    else if ((aClass = NSClassFromString(className)))
	return [NSBundle bundleForClass:aClass];
    else
	return nil;
}

- (NSArray *)dependencyClassNames;
{
    return dependencyClassNames;
}

- (NSArray *)modifyingBundledClasses;
{
    return modifyingBundledClasses;
}

// Actions

- (void)loadBundledClass;
{
    if (loaded)
	return;

    [NSThread lockMainThread];
    [bundleLock lock];

    if (loaded) {
	[bundleLock unlock];
        [NSThread unlockMainThread];
	return;
    }

    NS_DURING {
        [self loadDependencyClasses];

        if (bundle) {
            if (OFBundledClassDebug)
                NSLog(@"Class %@: loading from %@", className, bundle);
#ifdef OW_DISALLOW_DYNAMIC_LOADING
            if (!(bundleClass = NSClassFromString(className))) {
                NSLog(@"Dynamic load disallowed and class not hardlinked!");
                abort();
            }
#else
            bundleClass = [bundle classNamed:className];
            if (!bundleClass) {
                // If the class is in a framework which is linked into the bundle, then -[NSBundle classNamed:] won't find the class, but NSClassFromString() will.
                bundleClass = NSClassFromString(className);
            }
            if ([NSThread isMultiThreaded])
                [NSObject initializeAllClasses];
            [[NSNotificationCenter defaultCenter] postNotificationName:OFBundledClassDidLoadNotification object:bundle];
#endif
        } else {
            bundleClass = NSClassFromString(className);
            if (bundleClass) {
                if (OFBundledClassDebug)
                    NSLog(@"Class %@: found", className);
            }
        }

        [self loadModifierClasses];

        loaded = YES;

    } NS_HANDLER {
        NSLog(@"Error loading %@: %@", bundle, [localException reason]);
    } NS_ENDHANDLER;

    [bundleLock unlock];
    [NSThread unlockMainThread];
}

// Debugging

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    [debugDictionary setObject:className forKey:@"className"];
    [debugDictionary setObject:bundle forKey:@"bundle"];
    [debugDictionary setObject:dependencyClassNames forKey:@"dependencyClassNames"];
    [debugDictionary setObject:modifyingBundledClasses forKey:@"modifyingBundledClasses"];

    return debugDictionary;
}

- (NSString *)shortDescription;
{
    return [@"OFBundledClass " stringByAppendingString:className];
}

@end

@implementation OFBundledClass (Private)

+ (void)addImmediateLoadClass:(OFBundledClass *)aClass;
{
    [immediateLoadClasses addObject:aClass];
}

- initWithClassName:(NSString *)aClassName;
{
    if (![super init])
	return nil;

    className = [aClassName copy];
    bundle = nil;
    dependencyClassNames = [[NSMutableArray alloc] init];
    modifyingBundledClasses = [[NSMutableArray alloc] init];

    bundleClass = NSClassFromString(aClassName);
    loaded = bundleClass != nil;
    
    return self;
}

//

- (void)setBundle:(NSBundle *)aBundle;
{
    if (bundle == aBundle)
	return;
    [bundle release];
    bundle = [aBundle retain];
}

- (void)addDependencyClassNamed:(NSString *)aClassName;
{
    [dependencyClassNames addObject:aClassName];
}

- (void)addModifyingBundledClass:(OFBundledClass *)aBundledClass;
{
    [modifyingBundledClasses addObject:aBundledClass];
    if (loaded)
        [aBundledClass loadBundledClass];
}

- (void)addDependencyClassNames:(NSArray *)anArray;
{
    NSEnumerator *enumerator;
    NSString *dependency;
    
    enumerator = [anArray objectEnumerator];
    while ((dependency = [enumerator nextObject]))
	[self addDependencyClassNamed:dependency];
}

- (void)modifiesClassesNamed:(NSArray *)anArray;
{
    NSEnumerator *enumerator;
    NSString *modifiedClass;
    
    enumerator = [anArray objectEnumerator];
    while ((modifiedClass = [enumerator nextObject])) {
	OFBundledClass *bundledClass;

	bundledClass = [[self class] bundledClassNamed:modifiedClass];
	[bundledClass addModifyingBundledClass:self];
    }
}

//

- (void)loadDependencyClasses;
{
    NSEnumerator *enumerator;
    NSString *aClassName;

    if ([dependencyClassNames count] == 0)
	return;

    if (OFBundledClassDebug)
	NSLog(@"Class %@: loading dependencies", className);

    enumerator = [dependencyClassNames objectEnumerator];
    while ((aClassName = [enumerator nextObject]))
	[[[self class] classNamed:aClassName] loadBundledClass];
}

- (void)loadModifierClasses;
{
    NSEnumerator *enumerator;
    OFBundledClass *aClass;

    if ([modifyingBundledClasses count] == 0)
	return;

    if (OFBundledClassDebug)
	NSLog(@"Class %@: loading modifiers", className);
    
    enumerator = [modifyingBundledClasses objectEnumerator];
    while ((aClass = [enumerator nextObject]))
        [aClass loadBundledClass];
}

//

- (void)processDescription:(NSDictionary *)description;
{
    BOOL immediateLoad;

    [self addDependencyClassNames:[description objectForKey:@"dependsOnClasses"]];
    [self modifiesClassesNamed:[description objectForKey:@"modifiesClasses"]];
    immediateLoad = [description boolForKey:@"immediateLoad"];
    if (immediateLoad)
        [isa addImmediateLoadClass:self];
}

@end
