// 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 "NSObject-OFExtensions.h"

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

RCS_ID("$Header: /Network/Developer/Source/CVS/OmniGroup/OmniFoundation/OpenStepExtensions.subproj/NSObject-OFExtensions.m,v 1.10 1999/03/30 21:25:45 kc Exp $")

#if defined(DEBUG) && !defined(WIN32)
#define TIME_INITIALIZE
#import <sys/time.h>
#endif

@implementation NSObject (OFExtensions)

// +initializeAllClasses is necessary because class_initialize() isn't
// thread safe.  That is, a given +initialize can be called simultaneously
// in several threads, which not only confuses it, but can also confuse
// the runtime.

+ (void)didLoad;
{
    NSString *processName;

    // Call +initializeAllClasses when the app is about to go multithreaded.  This gives IB fits,
    // so don't do it in common utility applications.
    processName = [[NSProcessInfo processInfo] processName];
    if (![processName isEqualToString: @"InterfaceBuilder"] &&
        ![processName isEqualToString: @"EOModeler"])
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(initializeAllClasses)
                                                     name:NSWillBecomeMultiThreadedNotification
                                                   object:nil];
}


#define SKIP_CLASS_NAMED(aName) \
do { \
    Class aClass; \
 \
    aClass = NSClassFromString(aName); \
    if (aClass) \
        NSHashInsertKnownAbsent(skipClasssesHashTable, aClass); \
} while (0)

+ (void)initializeAllClasses;
{
    static NSHashTable *skipClasssesHashTable = NULL;
    NXHashTable *classes;
    NXHashState hashIterator;
    Class aClass, *theClasses;
    unsigned int initCount = 0, initLeft;
    Method initializeMethod;
    SEL initializeSelector = @selector(initialize);
#ifdef TIME_INITIALIZE
    struct timeval begintime, endtime;
    struct timezone dummy;
    unsigned int classCount = 0, noInit = 0;
#endif

    OMNI_POOL_START {

        if (!skipClasssesHashTable) {
            skipClasssesHashTable = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 5);
            SKIP_CLASS_NAMED(@"NSDataLinkManager");
            SKIP_CLASS_NAMED(@"NSInvocationBuilder");
            SKIP_CLASS_NAMED(@"NSMovieView");
            SKIP_CLASS_NAMED(@"OFForwardObject");
            SKIP_CLASS_NAMED(@"OFRetainableObject");
            SKIP_CLASS_NAMED(@"NMSScriptedClass");
        }

        // The reason for the multiple-pass algorithm is that a class could load another class in its +initialize method, invalidating our hash iterator.

        classes = objc_getClasses();
        hashIterator = NXInitHashState(classes);
        while (NXNextHashState(classes, &hashIterator, (void **)&aClass)) {
#ifdef TIME_INITIALIZE
            classCount++;
#endif
            if (CLS_GETINFO(aClass->isa, CLS_INITIALIZED) || NSHashGet(skipClasssesHashTable, aClass))
                continue;
            initCount++;
        }

        theClasses = NSZoneMalloc(NULL, initCount * sizeof(Class));
        hashIterator = NXInitHashState(classes);
        initCount = 0;
        while (NXNextHashState(classes, &hashIterator, (void **)&aClass)) {
            if (CLS_GETINFO(aClass->isa, CLS_INITIALIZED) ||
                NSHashGet(skipClasssesHashTable, aClass))
                continue;
            theClasses[initCount++] = aClass;
        }

        initLeft = initCount;

#ifdef TIME_INITIALIZE
        gettimeofday(&begintime, &dummy);
#endif

        while (initLeft) {
            aClass = theClasses[--initLeft];

            initializeMethod = class_getClassMethod(aClass, initializeSelector);
            if (initializeMethod) {
                [aClass class];
            }
#ifdef TIME_INITIALIZE
            else {
                noInit++;
            }
#endif
        };

#ifdef TIME_INITIALIZE
        gettimeofday(&endtime, &dummy);

        NSLog(@"Initialized %d classes out of %d (%d total) in %g sec.",
              initCount - noInit, initCount, classCount,
              (float)(endtime.tv_sec - begintime.tv_sec) +
              1e-6 * ((float)endtime.tv_usec - (float)begintime.tv_usec));
#endif

        NSZoneFree(NULL, theClasses);
    } OMNI_POOL_END;
}

static BOOL implementsInstanceMethod(struct objc_class *class, SEL aSelector)
{
    struct objc_method_list *mlist;
    int i;

    /* Check only this class, NOT any superclasses. */

#if defined(OBJC_NEXT_METHOD_LIST)
    void *iterator = 0;
    while ((mlist = class_nextMethodList(class, &iterator))) {
#else
    for (mlist = class->methods; mlist; mlist = mlist->method_next) {
#endif
        for (i = 0; i <mlist->method_count; i++) {
            if (mlist->method_list[i].method_name == aSelector)
                return YES;
        }
    }
    return NO;
}

+ (Class) classImplementingSelector: (SEL) aSelector;
{
    Class aClass = self;

    while (aClass) {
        if (implementsInstanceMethod(aClass, aSelector))
            return aClass;
        aClass = aClass->super_class;
    }

    return Nil;
}

@end



IMP OFReplaceMethodImplementation(Class aClass, SEL oldSelector, IMP newImp)
{
    struct objc_method *thisMethod;
    IMP                 oldImp = NULL;
    extern void         _objc_flush_caches(Class);

    if ((thisMethod = class_getInstanceMethod(aClass, oldSelector))) {
        oldImp = thisMethod->method_imp;

        // Replace the method in place
        thisMethod->method_imp = newImp;

        // Flush the method cache
        _objc_flush_caches(aClass);
    }

    return oldImp;
}

BOOL OFClassIsSubclassOfClass(Class subClass, Class superClass)
{
    while (subClass) {
        if (subClass == superClass)
            return YES;
        else
            subClass = subClass->super_class;
    }
    return NO;
}

