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}