001/*
002 * BridJ - Dynamic and blazing-fast native interop for Java.
003 * http://bridj.googlecode.com/
004 *
005 * Copyright (c) 2010-2013, Olivier Chafik (http://ochafik.com/)
006 * All rights reserved.
007 *
008 * Redistribution and use in source and binary forms, with or without
009 * modification, are permitted provided that the following conditions are met:
010 * 
011 *     * Redistributions of source code must retain the above copyright
012 *       notice, this list of conditions and the following disclaimer.
013 *     * Redistributions in binary form must reproduce the above copyright
014 *       notice, this list of conditions and the following disclaimer in the
015 *       documentation and/or other materials provided with the distribution.
016 *     * Neither the name of Olivier Chafik nor the
017 *       names of its contributors may be used to endorse or promote products
018 *       derived from this software without specific prior written permission.
019 * 
020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY
021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031package org.bridj.objc;
032
033import java.io.File;
034import java.io.FileNotFoundException;
035import java.io.IOException;
036import java.lang.reflect.Method;
037import java.lang.reflect.ParameterizedType;
038import java.util.*;
039
040import org.bridj.*;
041import static org.bridj.Pointer.*;
042import org.bridj.NativeEntities.Builder;
043import org.bridj.util.Utils;
044import org.bridj.ann.Library;
045import org.bridj.ann.Ptr;
046import java.lang.reflect.Modifier;
047import java.lang.reflect.Type;
048import org.bridj.Platform;
049import org.bridj.demangling.Demangler;
050
051/// http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html
052@Library("/usr/lib/libobjc.A.dylib")
053public class ObjectiveCRuntime extends CRuntime {
054
055    public boolean isAvailable() {
056        return Platform.isMacOSX();
057    }
058    Map<String, Pointer<? extends ObjCObject>> nativeClassesByObjCName = new HashMap<String, Pointer<? extends ObjCObject>>(),
059            nativeMetaClassesByObjCName = new HashMap<String, Pointer<? extends ObjCObject>>();
060
061    public ObjectiveCRuntime() {
062        BridJ.register();
063        rootCallbackClasses.add(ObjCBlock.class);
064    }
065
066    <T extends ObjCObject> T realCast(Pointer<? extends ObjCObject> id) {
067        if (id == null) {
068            return null;
069        }
070        Pointer<Byte> cn = object_getClassName(id);
071        if (cn == null) {
072            throw new RuntimeException("Null class name for this ObjectiveC object pointer !");
073        }
074
075        String n = cn.getCString();
076
077        Class<? extends ObjCObject> c = bridjClassesByObjCName.get(n);
078        if (c == null) {
079            throw new RuntimeException("Class " + n + " was not registered yet in the BridJ runtime ! (TODO : auto create by scanning path, then reflection !)");
080        }
081        return (T) id.getNativeObject(c);
082    }
083    /*
084
085     public static class Class extends Pointer<? extends ObjCObject> {
086
087     public Class(long peer) {
088     super(peer);
089     }
090
091     public Class(Pointer<?> ptr) {
092     super(ptr);
093     }
094     }*/
095
096    protected static native Pointer<? extends ObjCObject> object_getClass(Pointer<? extends ObjCObject> obj);
097
098    protected static native Pointer<? extends ObjCObject> objc_getClass(Pointer<Byte> name);
099
100    protected static native Pointer<? extends ObjCObject> objc_getMetaClass(Pointer<Byte> name);
101
102    protected static native Pointer<Byte> object_getClassName(Pointer<? extends ObjCObject> obj);
103
104    protected static native Pointer<? extends ObjCObject> class_createInstance(Pointer<? extends ObjCObject> cls, @Ptr long extraBytes);
105
106    public static native Pointer<? extends ObjCObject> objc_getProtocol(Pointer<Byte> name);
107
108    public static native boolean class_addProtocol(Pointer<? extends ObjCObject> cls, Pointer<? extends ObjCObject> protocol);
109
110    protected static native boolean class_respondsToSelector(Pointer<? extends ObjCObject> cls, SEL sel);
111
112    protected static native SEL sel_registerName(Pointer<Byte> name);
113
114    protected static native Pointer<Byte> sel_getName(SEL sel);
115
116    public String getMethodSignature(Method method) {
117        return getMethodSignature(method.getGenericReturnType(), method.getGenericParameterTypes());
118    }
119
120    public String getMethodSignature(Type returnType, Type... paramTypes) {
121        StringBuilder b = new StringBuilder();
122
123        b.append(getTypeSignature(returnType));
124        b.append(getTypeSignature(Pointer.class)); // implicit self
125        b.append(getTypeSignature(SEL.class)); // implicit selector
126        for (Type paramType : paramTypes) {
127            b.append(getTypeSignature(paramType));
128        }
129        return b.toString();
130    }
131
132    char getTypeSignature(Type type) {
133        Character c = signatureByType.get(type);
134        if (c == null) {
135            c = signatureByType.get(Utils.getClass(type));
136        }
137        if (c == null) {
138            throw new RuntimeException("Unknown type for Objective-C signatures : " + Utils.toString(type));
139        }
140        return c;
141    }
142    static final Map<Type, Character> signatureByType = new HashMap<Type, Character>();
143    static final Map<Character, List<Type>> typesBySignature = new HashMap<Character, List<Type>>();
144
145    static {
146        initSignatures();
147    }
148
149    static void addSignature(char sig, Type... types) {
150        List<Type> typesList = typesBySignature.get(sig);
151        if (typesList == null) {
152            typesBySignature.put(sig, typesList = new ArrayList<Type>());
153        }
154
155        for (Type type : types) {
156            signatureByType.put(type, sig);
157
158            if (type != null && !typesList.contains(type)) {
159                typesList.add(type);
160            }
161        }
162    }
163
164    static void initSignatures() {
165        boolean is32 = CLong.SIZE == 4;
166        addSignature('q', long.class, !is32 ? CLong.class : null);
167        addSignature('i', int.class, is32 ? CLong.class : null);
168        addSignature('I', int.class, is32 ? CLong.class : null);
169        addSignature('s', short.class, char.class);
170        addSignature('c', byte.class, boolean.class);
171        addSignature('f', float.class);
172        addSignature('d', double.class);
173        addSignature('v', void.class);
174        addSignature('@', Pointer.class);
175        addSignature(':', SEL.class);
176    }
177
178    synchronized Pointer<? extends ObjCObject> getObjCClass(String name, boolean meta) throws ClassNotFoundException {
179        if (name.equals("")) {
180            return null;
181        }
182        Map<String, Pointer<? extends ObjCObject>> map = meta ? nativeMetaClassesByObjCName : nativeClassesByObjCName;
183        Pointer<? extends ObjCObject> c = map.get(name);
184        if (c == null) {
185            Pointer<Byte> pName = pointerToCString(name);
186            c = meta ? objc_getMetaClass(pName) : objc_getClass(pName);
187            if (c != null) {
188                assert object_getClassName(c).getCString().equals(name);
189                map.put(name, c);
190            }
191        }
192        if (c == null) {
193            throw new ClassNotFoundException("Objective C class not found : " + name);
194        }
195
196        return c;
197    }
198
199    @Override
200    protected NativeLibrary getNativeLibrary(Class<?> type) throws IOException {
201        Library libAnn = type.getAnnotation(Library.class);
202        if (libAnn != null) {
203            try {
204                String name = libAnn.value();
205                return BridJ.getNativeLibrary(name, new File("/System/Library/Frameworks/" + name + ".framework/" + name));
206            } catch (FileNotFoundException ex) {
207            }
208        }
209
210        return super.getNativeLibrary(type);
211    }
212    Map<String, Class<? extends ObjCObject>> bridjClassesByObjCName = new HashMap<String, Class<? extends ObjCObject>>();
213
214    @Override
215    public void register(Type type) {
216        Class<?> typeClass = Utils.getClass(type);
217        typeClass.getAnnotation(Library.class);
218        Library libAnn = typeClass.getAnnotation(Library.class);
219        if (libAnn != null) {
220            String name = libAnn.value();
221            File libraryFile = BridJ.getNativeLibraryFile(name);
222            if (libraryFile != null) {
223                System.load(libraryFile.toString());
224            }
225            if (ObjCObject.class.isAssignableFrom(typeClass)) {
226                bridjClassesByObjCName.put(typeClass.getSimpleName(), (Class<? extends ObjCObject>) typeClass);
227            }
228        }
229
230        super.register(type);
231    }
232
233    public String getSelector(Method method) {
234        Selector selAnn = method.getAnnotation(Selector.class);
235        if (selAnn != null) {
236            return selAnn.value();
237        }
238
239        String n = Demangler.getMethodName(method);
240        if (n.endsWith("_")) {
241            n = n.substring(0, n.length() - 1);
242        }
243
244        if (method.getParameterTypes().length > 0) {
245            n += ":";
246        }
247
248        n = n.replace('_', ':');
249        return n;
250    }
251
252    @Override
253    protected void registerNativeMethod(
254            Class<?> type,
255            NativeLibrary typeLibrary,
256            Method method,
257            NativeLibrary methodLibrary,
258            Builder builder,
259            MethodCallInfoBuilder methodCallInfoBuilder) throws FileNotFoundException {
260
261        if (method == null) {
262            return;
263        }
264
265        if (!ObjCObject.class.isAssignableFrom(type) || ObjCBlock.class.isAssignableFrom(type)) {
266            super.registerNativeMethod(type, typeLibrary, method, methodLibrary, builder, methodCallInfoBuilder);
267            return;
268        }
269
270        try {
271            MethodCallInfo mci = methodCallInfoBuilder.apply(method);
272            boolean isStatic = Modifier.isStatic(method.getModifiers());
273
274            if (isStatic) {
275                Pointer<ObjCClass> pObjcClass = getObjCClass((Class) type).as(ObjCClass.class);
276                ObjCClass objcClass = pObjcClass.get();
277                mci.setNativeClass(pObjcClass.getPeer());
278            }
279
280            mci.setSymbolName(getSelector(method));
281            builder.addObjCMethod(mci);
282        } catch (ClassNotFoundException ex) {
283            throw new RuntimeException("Failed to register method " + method + " : " + ex, ex);
284        }
285    }
286
287    public static ObjectiveCRuntime getInstance() {
288        return BridJ.getRuntimeByRuntimeClass(ObjectiveCRuntime.class);
289    }
290
291    public static Type getBlockCallbackType(Class blockClass) {
292        if (!ObjCBlock.class.isAssignableFrom(blockClass) || ObjCBlock.class == blockClass) {
293            throw new RuntimeException("Class " + blockClass.getName() + " should be a subclass of " + ObjCBlock.class.getName());
294        }
295
296        Type p = blockClass.getGenericSuperclass();
297        if (Utils.getClass(p) == (Class) ObjCBlock.class) {
298            Type callbackType = Utils.getUniqueParameterizedTypeParameter(p);
299            if (callbackType == null || !(callbackType instanceof Class || callbackType instanceof ParameterizedType)) {
300                throw new RuntimeException("Class " + blockClass.getName() + " should inherit from " + ObjCBlock.class.getName() + " with a valid single type parameter (found " + Utils.toString(p) + ")");
301            }
302
303            return callbackType;
304        }
305        throw new RuntimeException("Unexpected failure in getBlockCallbackType");
306    }
307    static final Pointer.Releaser ObjCBlockReleaser = new Pointer.Releaser() {
308        public void release(Pointer p) {
309            ObjCJNI.releaseObjCBlock(p.getPeer());
310        }
311    };
312
313    @Override
314    public <T extends NativeObject> TypeInfo<T> getTypeInfo(final Type type) {
315        return new CTypeInfo<T>(type) {
316            @Override
317            public void initialize(T instance) {
318                if (!BridJ.isCastingNativeObjectInCurrentThread()) {
319                    if (instance instanceof ObjCBlock) {
320                        final Pointer<CallbackInterface> pcb = registerCallbackInstance((CallbackInterface) instance);
321                        ((ObjCBlock) instance).pCallback = pcb;
322
323                        Pointer<T> pBlock = pointerToAddress(ObjCJNI.createObjCBlockWithFunctionPointer(pcb.getPeer()), type);
324                        pBlock = pBlock.withReleaser(new Releaser() {
325                            public void release(Pointer<?> p) {
326                                pcb.release();
327                                ObjCJNI.releaseObjCBlock(p.getPeer());
328                            }
329                        });
330
331                        setNativeObjectPeer(instance, pBlock);
332                    } else {
333                        super.initialize(instance);
334                    }
335                } else {
336                    super.initialize(instance);
337                }
338            }
339
340            @Override
341            public void initialize(T instance, Pointer peer) {
342                if (instance instanceof ObjCClass) {
343                    setNativeObjectPeer(instance, peer);
344                } /*else if (instance instanceof ObjCBlock) {
345                 setNativeObjectPeer(instance, peer);
346                 ObjCBlock block = (ObjCBlock)instance;
347
348                 Type callbackType = getBlockCallbackType(instance.getClass());
349                 Pointer<Callback> p = pointerToAddress(ObjCJNI.getObjCBlockFunctionPointer(peer.getPeer()), callbackType);
350                 block.callback = p.get(); // TODO take type information somewhere
351                 } */ else {
352                    super.initialize(instance, peer);
353                }
354            }
355
356            @Override
357            public void initialize(T instance, int constructorId, Object... args) {
358                try {
359
360                    /*if (instance instanceof ObjCBlock) {
361                     Object firstArg;
362                     if (constructorId != ObjCBlock.CALLBACK_CONSTRUCTOR_ID || args.length != 1 || !((firstArg = args[0]) instanceof Callback))
363                     throw new RuntimeException("Block constructor should have id " + ObjCBlock.CALLBACK_CONSTRUCTOR_ID + " (got " + constructorId + " ) and only one callback argument (got " + args.length + ")");
364                                
365                     Callback cb = (Callback)firstArg;
366                     Pointer<Callback> pcb = getPointer(cb);
367                     Pointer<T> peer = (Pointer)pointerToAddress(ObjCJNI.createObjCBlockWithFunctionPointer(pcb.getPeer()), type).withReleaser(ObjCBlockReleaser);;
368                     if (peer == null)
369                     throw new RuntimeException("Failed to create Objective-C block for callback " + cb);
370                                
371                     setNativeObjectPeer(instance, peer);
372                     } else*/ {
373                        Pointer<? extends ObjCObject> c = ObjectiveCRuntime.this.getObjCClass(typeClass);
374                        if (c == null) {
375                            throw new RuntimeException("Failed to get Objective-C class for type " + typeClass.getName());
376                        }
377                        Pointer<ObjCClass> pc = c.as(ObjCClass.class);
378                        Pointer<ObjCObject> p = pc.get().new$(); //.alloc();
379                        if (constructorId == -1) {
380                            p = p.get().init();
381                        } else {
382                            throw new UnsupportedOperationException("TODO handle constructors !");
383                        }
384                        setNativeObjectPeer(instance, p);
385                    }
386                } catch (ClassNotFoundException ex) {
387                    throw new RuntimeException("Failed to initialize instance of type " + Utils.toString(type) + " : " + ex, ex);
388                }
389            }
390        };
391    }
392
393    public static Pointer<? extends ObjCObject> getObjCClass(String name) throws ClassNotFoundException {
394        return getInstance().getObjCClass(name, false);
395    }
396
397    private Pointer<? extends ObjCObject> getObjCClass(Class<? extends NativeObject> cls) throws ClassNotFoundException {
398        if (cls == ObjCClass.class) {
399            return getObjCClass("NSObject", true);
400        } else if (cls == ObjCObject.class) {
401            return getObjCClass("NSObject", false);
402        }
403
404        return getObjCClass(cls.getSimpleName(), false);
405    }
406}