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 }