1/*
2 * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "objc_class.h"
28
29#include "objc_instance.h"
30#include "WebScriptObject.h"
31
32namespace JSC {
33namespace Bindings {
34
35static void deleteMethod(CFAllocatorRef, const void* value)
36{
37    delete static_cast<const Method*>(value);
38}
39
40static void deleteField(CFAllocatorRef, const void* value)
41{
42    delete static_cast<const Field*>(value);
43}
44
45const CFDictionaryValueCallBacks MethodDictionaryValueCallBacks = { 0, 0, &deleteMethod, 0 , 0 };
46const CFDictionaryValueCallBacks FieldDictionaryValueCallBacks = { 0, 0, &deleteField, 0 , 0 };
47
48ObjcClass::ObjcClass(ClassStructPtr aClass)
49    : _isa(aClass)
50    , _methods(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &MethodDictionaryValueCallBacks))
51    , _fields(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &FieldDictionaryValueCallBacks))
52{
53}
54
55static CFMutableDictionaryRef classesByIsA = 0;
56
57static void _createClassesByIsAIfNecessary()
58{
59    if (!classesByIsA)
60        classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
61}
62
63ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa)
64{
65    _createClassesByIsAIfNecessary();
66
67    ObjcClass* aClass = (ObjcClass*)CFDictionaryGetValue(classesByIsA, isa);
68    if (!aClass) {
69        aClass = new ObjcClass(isa);
70        CFDictionaryAddValue(classesByIsA, isa, aClass);
71    }
72
73    return aClass;
74}
75
76MethodList ObjcClass::methodsNamed(const Identifier& identifier, Instance*) const
77{
78    MethodList methodList;
79    char fixedSizeBuffer[1024];
80    char* buffer = fixedSizeBuffer;
81    CString jsName = identifier.ascii();
82    if (!convertJSMethodNameToObjc(jsName.data(), buffer, sizeof(fixedSizeBuffer))) {
83        int length = jsName.length() + 1;
84        buffer = new char[length];
85        if (!buffer || !convertJSMethodNameToObjc(jsName.data(), buffer, length))
86            return methodList;
87    }
88
89
90    RetainPtr<CFStringRef> methodName(AdoptCF, CFStringCreateWithCString(NULL, buffer, kCFStringEncodingASCII));
91    Method* method = (Method*)CFDictionaryGetValue(_methods.get(), methodName.get());
92    if (method) {
93        methodList.append(method);
94        return methodList;
95    }
96
97    ClassStructPtr thisClass = _isa;
98    while (thisClass && methodList.isEmpty()) {
99#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
100        unsigned numMethodsInClass = 0;
101        MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass);
102#else
103        void* iterator = 0;
104        struct objc_method_list* objcMethodList;
105        while ((objcMethodList = class_nextMethodList(thisClass, &iterator))) {
106            unsigned numMethodsInClass = objcMethodList->method_count;
107#endif
108            for (unsigned i = 0; i < numMethodsInClass; i++) {
109#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
110                MethodStructPtr objcMethod = objcMethodList[i];
111                SEL objcMethodSelector = method_getName(objcMethod);
112#else
113                struct objc_method* objcMethod = &objcMethodList->method_list[i];
114                SEL objcMethodSelector = objcMethod->method_name;
115#endif
116                const char* objcMethodSelectorName = sel_getName(objcMethodSelector);
117                NSString* mappedName = nil;
118
119                // See if the class wants to exclude the selector from visibility in JavaScript.
120                if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)])
121                    if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector])
122                        continue;
123
124                // See if the class want to provide a different name for the selector in JavaScript.
125                // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
126                // of the class.
127                if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)])
128                    mappedName = [thisClass webScriptNameForSelector:objcMethodSelector];
129
130                if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer) == 0) {
131                    Method* aMethod = new ObjcMethod(thisClass, objcMethodSelector); // deleted when the dictionary is destroyed
132                    CFDictionaryAddValue(_methods.get(), methodName.get(), aMethod);
133                    methodList.append(aMethod);
134                    break;
135                }
136            }
137#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
138            thisClass = class_getSuperclass(thisClass);
139            free(objcMethodList);
140#else
141        }
142        thisClass = thisClass->super_class;
143#endif
144    }
145
146    if (buffer != fixedSizeBuffer)
147        delete [] buffer;
148
149    return methodList;
150}
151
152Field* ObjcClass::fieldNamed(const Identifier& identifier, Instance* instance) const
153{
154    ClassStructPtr thisClass = _isa;
155
156    CString jsName = identifier.ascii();
157    RetainPtr<CFStringRef> fieldName(AdoptCF, CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII));
158    Field* aField = (Field*)CFDictionaryGetValue(_fields.get(), fieldName.get());
159    if (aField)
160        return aField;
161
162    id targetObject = (static_cast<ObjcInstance*>(instance))->getObject();
163    id attributes = [targetObject attributeKeys];
164    if (attributes) {
165        // Class overrides attributeKeys, use that array of key names.
166        unsigned count = [attributes count];
167        for (unsigned i = 0; i < count; i++) {
168            NSString* keyName = [attributes objectAtIndex:i];
169            const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names.
170
171            // See if the class wants to exclude the selector from visibility in JavaScript.
172            if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
173                if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName])
174                    continue;
175
176            // See if the class want to provide a different name for the selector in JavaScript.
177            // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
178            // of the class.
179            NSString* mappedName = nil;
180            if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
181                mappedName = [thisClass webScriptNameForKey:UTF8KeyName];
182
183            if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) {
184                aField = new ObjcField((CFStringRef)keyName); // deleted when the dictionary is destroyed
185                CFDictionaryAddValue(_fields.get(), fieldName.get(), aField);
186                break;
187            }
188        }
189    } else {
190        // Class doesn't override attributeKeys, so fall back on class runtime
191        // introspection.
192
193        while (thisClass) {
194#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
195            unsigned numFieldsInClass = 0;
196            IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass);
197#else
198            struct objc_ivar_list* fieldsInClass = thisClass->ivars;
199            if (fieldsInClass) {
200                unsigned numFieldsInClass = fieldsInClass->ivar_count;
201#endif
202                for (unsigned i = 0; i < numFieldsInClass; i++) {
203#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
204                    IvarStructPtr objcIVar = ivarsInClass[i];
205                    const char* objcIvarName = ivar_getName(objcIVar);
206#else
207                    IvarStructPtr objcIVar = &fieldsInClass->ivar_list[i];
208                    const char* objcIvarName = objcIVar->ivar_name;
209#endif
210                    NSString* mappedName = 0;
211
212                    // See if the class wants to exclude the selector from visibility in JavaScript.
213                    if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
214                        if ([thisClass isKeyExcludedFromWebScript:objcIvarName])
215                            continue;
216
217                    // See if the class want to provide a different name for the selector in JavaScript.
218                    // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
219                    // of the class.
220                    if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
221                        mappedName = [thisClass webScriptNameForKey:objcIvarName];
222
223                    if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, jsName.data()) == 0) {
224                        aField = new ObjcField(objcIVar); // deleted when the dictionary is destroyed
225                        CFDictionaryAddValue(_fields.get(), fieldName.get(), aField);
226                        break;
227                    }
228                }
229#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
230            thisClass = class_getSuperclass(thisClass);
231            free(ivarsInClass);
232#else
233            }
234            thisClass = thisClass->super_class;
235#endif
236        }
237    }
238
239    return aField;
240}
241
242JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, const Identifier &propertyName)
243{
244    ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance);
245    id targetObject = objcInstance->getObject();
246
247    if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
248        return jsUndefined();
249    return new (exec) ObjcFallbackObjectImp(exec, exec->lexicalGlobalObject(), objcInstance, propertyName);
250}
251
252}
253}
254