// Copyright 1997-1998 Omni Development, Inc.  All rights reserved.

#import "OBPostLoader.h"

#import <Foundation/Foundation.h>
#import <objc/hashtable.h>
#import <objc/objc-class.h>
#import <objc/objc-runtime.h>
#import "rcsid.h"

RCS_ID("$Header: /LocalDeveloper/Source/CVS/OmniGroup/OmniBase/OBPostLoader.m,v 1.9 1998/01/12 06:53:10 kc Exp $")

@interface OBPostLoader (PrivateAPI)
+ (void)_sendSelectorToAllCategoriesOfAllClasses:(SEL)selectorToCall;
+ (void)_bundleDidLoad:(NSNotification *)notification;
@end

@implementation OBPostLoader

static NSHashTable *calledImplementations;

+ (void)initialize;
{
    static BOOL           initialized = NO;
    NSNotificationCenter *center;
    
    [super initialize];

    if (initialized)
	return;

    // Make sure we don't fight with NSBundle
    [NSString initialize];
    [NSBundle initialize];

    calledImplementations = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0);

    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(_bundleDidLoad:) name:NSBundleDidLoadNotification object:nil];
}

+ (void)processClasses;
{
    [self _sendSelectorToAllCategoriesOfAllClasses:@selector(performPosing)];
    [self _sendSelectorToAllCategoriesOfAllClasses:@selector(didLoad)];
}

@end

@implementation OBPostLoader (PrivateAPI)

+ (void)_sendSelectorToAllCategoriesOfAllClasses:(SEL)selectorToCall;
{
    NXHashTable *classHash;
    NXHashState state;
    Class aClass;

    // Loop through the class methods looking for implementations of didLoadSelector
    classHash = objc_getClasses();
    state = NXInitHashState(classHash);
    while (NXNextHashState(classHash, &state, (void **)&aClass)) {
        Class metaClass = aClass->isa; // we are looking at class methods
        void *iterator = 0;
        struct objc_method_list *mlist;

        while ((mlist = class_nextMethodList(metaClass, &iterator))) {
            struct objc_method *currentMethod;
            unsigned int remainingMethods;

            remainingMethods = mlist->method_count;
            currentMethod = &mlist->method_list[0];

            // It appears that the ObjC runtime is not thread-safe with respect to dyld.  One thread can be in the middle of looking up something in the method cache while another thread is causing dyld lazy binding to occur (and thus, it is modifying the method cache).  Since each method list cannot span a .o file, this will cause a lookup in each ObjC module, which in turn will cause dyld to do its stuff. Additionally, if the first method you call in any one module hasn't been looked up (usually a +didLoad method) you can run into crasher bug due to dyld not having chewed on your code yet.
            //
            // Since we don't know if the first method is a class method or instance method, we'll do the lookup on both.  This may be unecessary, but it can't hurt anything but a _tiny_ bit of performance.

            class_getInstanceMethod(aClass, currentMethod->method_name);
            class_getInstanceMethod(metaClass, currentMethod->method_name);

            while (remainingMethods--) {
                if (currentMethod->method_name == selectorToCall) {
                    IMP imp;

                    imp = currentMethod->method_imp;

                    // Call this implementation if it hasn't already been called
                    if (!NSHashGet(calledImplementations, imp)) {
                        NSAutoreleasePool *pool;

                        NSHashInsertKnownAbsent(calledImplementations, imp);
                        pool = [[NSAutoreleasePool alloc] init];
                        [aClass class]; // Ensure +initialize was called
                        imp(aClass, selectorToCall);
                        [pool release];
                    }
                }

                currentMethod++;
            }
        }
    }
}

+ (void)_bundleDidLoad:(NSNotification *)notification;
{
    [self processClasses];
}

@end
