001    package org.bridj.objc;
002    
003    import java.io.File;
004    import java.io.FileNotFoundException;
005    import java.io.IOException;
006    import java.lang.reflect.Method;
007    import java.lang.reflect.ParameterizedType;
008    import java.util.*;
009    
010    import org.bridj.*;
011    import static org.bridj.Pointer.*;
012    import org.bridj.NativeEntities.Builder;
013    import org.bridj.util.Utils;
014    import org.bridj.ann.Library;
015    import org.bridj.ann.Ptr;
016    import java.lang.reflect.Modifier;
017    import java.lang.reflect.Type;
018    import org.bridj.Platform;
019    
020    /// http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html
021    @Library("/usr/lib/libobjc.A.dylib")
022    public class ObjectiveCRuntime extends CRuntime {
023    
024        public boolean isAvailable() {
025            return Platform.isMacOSX();
026        }
027        Map<String, Pointer<? extends ObjCObject>> 
028                    nativeClassesByObjCName = new HashMap<String, Pointer<? extends ObjCObject>>(),
029                    nativeMetaClassesByObjCName = new HashMap<String, Pointer<? extends ObjCObject>>();
030    
031        public ObjectiveCRuntime() {
032            BridJ.register();
033            rootCallbackClasses.add(ObjCBlock.class);
034        }
035    
036        <T extends ObjCObject> T realCast(Pointer<? extends ObjCObject> id) {
037            if (id == null)
038                return null;
039            Pointer<Byte> cn = object_getClassName(id);
040            if (cn == null)
041                throw new RuntimeException("Null class name for this ObjectiveC object pointer !");
042    
043            String n = cn.getCString();
044    
045            Class<? extends ObjCObject> c = bridjClassesByObjCName.get(n);
046            if (c == null)
047                throw new RuntimeException("Class " + n + " was not registered yet in the BridJ runtime ! (TODO : auto create by scanning path, then reflection !)");
048            return (T)id.getNativeObject(c);
049        }
050        /*
051    
052        public static class Class extends Pointer<? extends ObjCObject> {
053    
054            public Class(long peer) {
055                super(peer);
056            }
057    
058            public Class(Pointer<?> ptr) {
059                super(ptr);
060            }
061        }*/
062    
063        protected static native Pointer<? extends ObjCObject> object_getClass(Pointer<? extends ObjCObject> obj);
064    
065        protected static native Pointer<? extends ObjCObject> objc_getClass(Pointer<Byte> name);
066    
067        protected static native Pointer<? extends ObjCObject> objc_getMetaClass(Pointer<Byte> name);
068    
069        protected static native Pointer<Byte> object_getClassName(Pointer<? extends ObjCObject> obj);
070    
071        protected static native Pointer<? extends ObjCObject> class_createInstance(Pointer<? extends ObjCObject> cls, @Ptr long extraBytes);
072        
073        public static native Pointer<? extends ObjCObject> objc_getProtocol(Pointer<Byte> name);
074        
075        public static native boolean class_addProtocol(Pointer<? extends ObjCObject> cls, Pointer<? extends ObjCObject> protocol);
076        
077        protected static native boolean class_respondsToSelector(Pointer<? extends ObjCObject> cls, SEL sel);
078    
079        protected static native SEL sel_registerName(Pointer<Byte> name);
080        protected static native Pointer<Byte> sel_getName(SEL sel);
081        
082        public String getMethodSignature(Method method) {
083                    return getMethodSignature(method.getGenericReturnType(), method.getGenericParameterTypes());
084        }
085        public String getMethodSignature(Type returnType, Type... paramTypes) {
086                    StringBuilder b = new StringBuilder();
087                    
088                    b.append(getTypeSignature(returnType));
089                    b.append(getTypeSignature(Pointer.class)); // implicit self
090                    b.append(getTypeSignature(SEL.class)); // implicit selector
091                    for (Type paramType : paramTypes)
092                b.append(getTypeSignature(paramType));
093                    return b.toString();
094        }
095        
096        char getTypeSignature(Type type) {
097                    Character c = signatureByType.get(type);
098                    if (c == null)
099                            c = signatureByType.get(Utils.getClass(type));
100                    if (c == null)
101                            throw new RuntimeException("Unknown type for Objective-C signatures : " + Utils.toString(type));
102                    return c;
103        }
104        
105        static final Map<Type, Character> signatureByType = new HashMap<Type, Character>();
106        static final Map<Character, List<Type>> typesBySignature = new HashMap<Character, List<Type>>();
107        static {
108                    initSignatures();
109        }
110        
111        static void addSignature(char sig, Type... types) {
112                    List<Type> typesList = typesBySignature.get(sig);
113                    if (typesList == null)
114                            typesBySignature.put(sig, typesList = new ArrayList<Type>());
115                    
116                    for (Type type : types) {
117                            signatureByType.put(type, sig);
118                    
119                            if (type != null && !typesList.contains(type))
120                                    typesList.add(type);
121                    }
122        }
123        static void initSignatures() {
124                    boolean is32 = CLong.SIZE == 4;
125                    addSignature('q', long.class, !is32 ? CLong.class : null);
126                    addSignature('i', int.class, is32 ? CLong.class : null);
127                    addSignature('I', int.class, is32 ? CLong.class : null);
128                    addSignature('s', short.class, char.class);
129                    addSignature('c', byte.class, boolean.class);
130                    addSignature('f', float.class);
131                    addSignature('d', double.class);
132                    addSignature('v', void.class);
133                    addSignature('@', Pointer.class);
134                    addSignature(':', SEL.class);
135        }
136        
137        synchronized Pointer<? extends ObjCObject> getObjCClass(String name, boolean meta) throws ClassNotFoundException {
138            if (name.equals(""))
139                return null;
140                    Map<String, Pointer<? extends ObjCObject>> map = meta ? nativeMetaClassesByObjCName : nativeClassesByObjCName; 
141            Pointer<? extends ObjCObject> c = map.get(name);
142            if (c == null) {
143                            Pointer<Byte> pName = pointerToCString(name);
144                            c = meta ? objc_getMetaClass(pName) : objc_getClass(pName);
145                if (c != null) {
146                    assert object_getClassName(c).getCString().equals(name);
147                    map.put(name, c);
148                }
149            }
150            if (c == null)
151                            throw new ClassNotFoundException("Objective C class not found : " + name);
152            
153            return c;
154        }
155    
156        @Override
157        protected NativeLibrary getNativeLibrary(Class<?> type) throws IOException {
158            Library libAnn = type.getAnnotation(Library.class);
159            if (libAnn != null) {
160                try {
161                    String name = libAnn.value();
162                    return BridJ.getNativeLibrary(name, new File("/System/Library/Frameworks/" + name + ".framework/" + name));
163                } catch (FileNotFoundException ex) {
164                }
165            }
166    
167            return super.getNativeLibrary(type);
168        }
169    
170        Map<String, Class<? extends ObjCObject>> bridjClassesByObjCName = new HashMap<String, Class<? extends ObjCObject>>();
171    
172        @Override
173        public void register(Type type) {
174            Class<?> typeClass = Utils.getClass(type);
175            typeClass.getAnnotation(Library.class);
176            Library libAnn = typeClass.getAnnotation(Library.class);
177            if (libAnn != null) {
178                String name = libAnn.value();
179                File libraryFile = BridJ.getNativeLibraryFile(name);
180                if (libraryFile != null) {
181                    System.load(libraryFile.toString());
182                }
183                if (ObjCObject.class.isAssignableFrom(typeClass)) {
184                    bridjClassesByObjCName.put(typeClass.getSimpleName(), (Class<? extends ObjCObject>)typeClass);
185                }
186            }
187    
188            super.register(type);
189        }
190    
191        public String getSelector(Method method) {
192                    Selector selAnn = method.getAnnotation(Selector.class);
193                    if (selAnn != null)
194                            return selAnn.value();
195                
196                    String n = method.getName();
197                    if (n.endsWith("_"))
198                            n = n.substring(0, n.length() - 1);
199                    
200                    if (method.getParameterTypes().length > 0)
201                            n += ":";
202                                    
203                    n = n.replace('_', ':');
204                    return n;
205            }
206            
207        @Override
208        protected void registerNativeMethod(
209                            Class<?> type,
210                NativeLibrary typeLibrary, 
211                Method method,
212                NativeLibrary methodLibrary, 
213                Builder builder, 
214                MethodCallInfoBuilder methodCallInfoBuilder
215                    ) throws FileNotFoundException 
216            {
217    
218            if (method == null)
219                return;
220    
221            if (!ObjCObject.class.isAssignableFrom(type) || ObjCBlock.class.isAssignableFrom(type)) {
222                super.registerNativeMethod(type, typeLibrary, method, methodLibrary, builder, methodCallInfoBuilder);
223                return;
224            }
225            
226            try {
227                MethodCallInfo mci = methodCallInfoBuilder.apply(method);
228                boolean isStatic = Modifier.isStatic(method.getModifiers());
229                
230                if (isStatic) {
231                    Pointer<ObjCClass> pObjcClass = getObjCClass((Class) type).as(ObjCClass.class);
232                                    ObjCClass objcClass = pObjcClass.get();
233                                    mci.setNativeClass(pObjcClass.getPeer());
234                }
235    
236                            mci.setSymbolName(getSelector(method));
237                builder.addObjCMethod(mci);
238            } catch (ClassNotFoundException ex) {
239                throw new RuntimeException("Failed to register method " + method + " : " + ex, ex);
240            }
241        }
242    
243        public static ObjectiveCRuntime getInstance() {
244            return BridJ.getRuntimeByRuntimeClass(ObjectiveCRuntime.class);
245        }
246        
247        public static Type getBlockCallbackType(Class blockClass) {
248            if (!ObjCBlock.class.isAssignableFrom(blockClass) || ObjCBlock.class == blockClass)
249                throw new RuntimeException("Class " + blockClass.getName() + " should be a subclass of " + ObjCBlock.class.getName());
250                
251            Type p = blockClass.getGenericSuperclass();
252            if (Utils.getClass(p) == (Class)ObjCBlock.class) {
253                            Type callbackType = Utils.getUniqueParameterizedTypeParameter(p);
254                            if (callbackType == null || !(callbackType instanceof Class || callbackType instanceof ParameterizedType))
255                                    throw new RuntimeException("Class " + blockClass.getName() + " should inherit from " + ObjCBlock.class.getName() + " with a valid single type parameter (found " + Utils.toString(p) + ")");
256                                            
257                            return callbackType;
258            }
259            throw new RuntimeException("Unexpected failure in getBlockCallbackType");
260        }
261                            
262            static final Pointer.Releaser ObjCBlockReleaser = new Pointer.Releaser() {
263                    public void release(Pointer p) {
264                            ObjCJNI.releaseObjCBlock(p.getPeer());
265                    }
266            };
267            
268        @Override
269        public <T extends NativeObject> TypeInfo<T> getTypeInfo(final Type type) {
270            return new CTypeInfo<T>(type) {
271    
272                @Override
273                public void initialize(T instance) {
274                    if (!BridJ.isCastingNativeObjectInCurrentThread()) {
275                        if (instance instanceof ObjCBlock) {
276                            final Pointer<CallbackInterface> pcb = registerCallbackInstance((CallbackInterface)instance);
277                            ((ObjCBlock)instance).pCallback = pcb;
278    
279                            Pointer<T> pBlock = pointerToAddress(ObjCJNI.createObjCBlockWithFunctionPointer(pcb.getPeer()), type);
280                            pBlock = pBlock.withReleaser(new Releaser() {
281                                public void release(Pointer<?> p) {
282                                    pcb.release();
283                                    ObjCJNI.releaseObjCBlock(p.getPeer());
284                                }
285                            });
286    
287                            setNativeObjectPeer(instance, pBlock);
288                        } else {
289                            super.initialize(instance);
290                        }
291                    } else {
292                        super.initialize(instance);
293                    }
294                }
295    
296                
297                @Override
298                public void initialize(T instance, Pointer peer) {
299                    if (instance instanceof ObjCClass) {
300                        setNativeObjectPeer(instance, peer);
301                    } /*else if (instance instanceof ObjCBlock) {
302                        setNativeObjectPeer(instance, peer);
303                        ObjCBlock block = (ObjCBlock)instance;
304    
305                        Type callbackType = getBlockCallbackType(instance.getClass());
306                        Pointer<Callback> p = pointerToAddress(ObjCJNI.getObjCBlockFunctionPointer(peer.getPeer()), callbackType);
307                        block.callback = p.get(); // TODO take type information somewhere
308                    } */else {
309                        super.initialize(instance, peer);
310                    }
311                }
312                
313                @Override
314                public void initialize(T instance, int constructorId, Object... args) {
315                    try {
316                            
317                        /*if (instance instanceof ObjCBlock) {
318                                    Object firstArg;
319                                    if (constructorId != ObjCBlock.CALLBACK_CONSTRUCTOR_ID || args.length != 1 || !((firstArg = args[0]) instanceof Callback))
320                                            throw new RuntimeException("Block constructor should have id " + ObjCBlock.CALLBACK_CONSTRUCTOR_ID + " (got " + constructorId + " ) and only one callback argument (got " + args.length + ")");
321                                    
322                                    Callback cb = (Callback)firstArg;
323                                    Pointer<Callback> pcb = pointerTo(cb);
324                                    Pointer<T> peer = (Pointer)pointerToAddress(ObjCJNI.createObjCBlockWithFunctionPointer(pcb.getPeer()), type).withReleaser(ObjCBlockReleaser);;
325                                    if (peer == null)
326                                            throw new RuntimeException("Failed to create Objective-C block for callback " + cb);
327                                    
328                                    setNativeObjectPeer(instance, peer);
329                        } else*/ {
330                                                    Pointer<? extends ObjCObject> c = ObjectiveCRuntime.this.getObjCClass(typeClass);
331                                                    if (c == null) {
332                                                            throw new RuntimeException("Failed to get Objective-C class for type " + typeClass.getName());
333                                                    }
334                                                    Pointer<ObjCClass> pc = c.as(ObjCClass.class);
335                                                    Pointer<ObjCObject> p = pc.get().new$(); //.alloc();
336                                                    if (constructorId == -1) {
337                                                            p = p.get().init();
338                                                    } else {
339                                                            throw new UnsupportedOperationException("TODO handle constructors !");
340                                                    }
341                                                    setNativeObjectPeer(instance, p);
342                                            }
343                    } catch (ClassNotFoundException ex) {
344                        throw new RuntimeException("Failed to initialize instance of type " + Utils.toString(type) + " : " + ex, ex);
345                    }
346                }
347            };
348        }
349    
350        public static Pointer<? extends ObjCObject> getObjCClass(String name) throws ClassNotFoundException {
351            return getInstance().getObjCClass(name, false);
352        }
353        private Pointer<? extends ObjCObject> getObjCClass(Class<? extends NativeObject> cls) throws ClassNotFoundException {
354                    if (cls == ObjCClass.class)
355                            return getObjCClass("NSObject", true);
356                else if (cls == ObjCObject.class)
357                            return getObjCClass("NSObject", false);
358                    
359                    return getObjCClass(cls.getSimpleName(), false);
360        }
361    }