Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.bugvm.objc.ObjCClass Maven / Gradle / Ivy
/*
* Copyright (C) 2012 RoboVM AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.bugvm.objc;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.bugvm.objc.annotation.BindSelector;
import com.bugvm.objc.annotation.CustomClass;
import com.bugvm.objc.annotation.NativeClass;
import com.bugvm.objc.annotation.NativeProtocolProxy;
import com.bugvm.objc.annotation.TypeEncoding;
import com.bugvm.rt.VM;
import com.bugvm.rt.bro.Bro;
import com.bugvm.rt.bro.annotation.Callback;
import com.bugvm.rt.bro.annotation.Library;
import com.bugvm.rt.bro.annotation.MarshalsPointer;
import com.bugvm.rt.bro.ptr.IntPtr;
@Library("objc")
public final class ObjCClass extends ObjCObject {
private static final String OBJC_PROXY_CLASS_SUFFIX = "$ObjCProxy";
private static final int OBJC_PROXY_CLASS_SUFFIX_LENGTH = OBJC_PROXY_CLASS_SUFFIX.length();
private static final Map, ObjCClass> typeToClass = new HashMap, ObjCClass>();
private static final Map nameToClass = new HashMap();
private static final Map> allNativeClasses = new HashMap<>();
private static final Map> allNativeProtocolProxies = new HashMap<>();
private static final Map> allCustomClasses = new HashMap<>();
static final Map> allObjCProxyClasses = new HashMap<>();
private static final int ACC_SYNTHETIC = 0x1000;
private static final String CUSTOM_CLASS_NAME_PREFIX = "j_";
static {
ObjCRuntime.bind(ObjCClass.class);
@SuppressWarnings("unchecked")
Class extends ObjCObject>[] classes = (Class extends ObjCObject>[])
VM.listClasses(ObjCObject.class, ClassLoader.getSystemClassLoader());
for (Class extends ObjCObject> cls : classes) {
NativeClass nativeClassAnno = cls.getAnnotation(NativeClass.class);
if (nativeClassAnno != null) {
String name = nativeClassAnno.value();
if (name.length() == 0) {
name = cls.getSimpleName();
}
allNativeClasses.put(name, cls);
} else {
NativeProtocolProxy nativeProtocolProxyAnno = cls.getAnnotation(NativeProtocolProxy.class);
if (nativeProtocolProxyAnno != null) {
String name = nativeProtocolProxyAnno.value();
if (name.length() == 0) {
name = cls.getSimpleName();
}
allNativeProtocolProxies.put(name, cls);
} else {
CustomClass customClassAnno = cls.getAnnotation(CustomClass.class);
String name = cls.getName();
if (customClassAnno != null) {
String value = customClassAnno.value();
if (value.length() > 0) {
name = value;
}
} else if (name.indexOf('.') == -1) {
name = "." + name;
}
allCustomClasses.put(name, cls);
}
}
if (isObjCProxy(cls)) {
// Map protocol interface names to ObjC protocol proxy classes
String name = cls.getName();
String protocolName = name.substring(0, name.length() - OBJC_PROXY_CLASS_SUFFIX_LENGTH);
allObjCProxyClasses.put(protocolName, cls);
}
}
}
static boolean isObjCProxy(Class> cls) {
return (cls.getModifiers() & ACC_SYNTHETIC) > 0 && cls.getName().endsWith(OBJC_PROXY_CLASS_SUFFIX);
}
public static class Marshaler {
@MarshalsPointer
public static Class extends ObjCObject> toObject(Class cls, long handle, long flags) {
ObjCClass o = ObjCClass.toObjCClass(handle);
if (o == null) {
return null;
}
return o.getType();
}
@MarshalsPointer
public static long toNative(Class extends ObjCObject> o, long flags) {
if (o == null) {
return 0L;
}
ObjCClass c = ObjCClass.getByType(o);
return c.getHandle();
}
}
private final Class extends ObjCObject> type;
private final String name;
private final boolean custom;
private final boolean protocol;
private ObjCClass(long handle, Class extends ObjCObject> type, String name, boolean custom, boolean protocol) {
super(handle, false);
this.type = type;
this.name = name;
this.custom = custom;
this.protocol = protocol;
}
public Class extends ObjCObject> getType() {
return type;
}
public String getName() {
return name;
}
public boolean isCustom() {
return custom;
}
public boolean isProtocol() {
return protocol;
}
@Override
public String toString() {
return type.getName();
}
public String toDebugString() {
StringBuilder sb = new StringBuilder();
sb.append("@interface ").append(getName());
long superclass = ObjCRuntime.class_getSuperclass(getHandle());
if (superclass != 0) {
sb.append(" : ").append(VM.newStringUTF(ObjCRuntime.class_getName(superclass)));
}
IntPtr outCount = new IntPtr();
long protocols = ObjCRuntime.class_copyProtocolList(getHandle(), outCount.getHandle());
if (outCount.get() > 0) {
sb.append(" <");
for (int i = 0; i < outCount.get(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(VM.newStringUTF(ObjCRuntime.protocol_getName(VM.getPointer(protocols))));
protocols += (Bro.IS_64BIT ? 8 : 4);
}
sb.append('>');
}
sb.append('\n');
sb.append("@end");
return sb.toString();
}
public static ObjCClass getByName(String objcClassName) {
synchronized (objcBridgeLock) {
ObjCClass c = nameToClass.get(objcClassName);
if (c == null) {
c = getByNameNotLoaded(objcClassName);
if (c == null) {
throw new ObjCClassNotFoundException("Could not find Java class corresponding to Objective-C class: " + objcClassName);
}
}
return c;
}
}
private static ObjCClass getByNameNotLoaded(String objcClassName) {
Class extends ObjCObject> cls = allNativeClasses.get(objcClassName);
if (cls != null) {
return getByType(cls);
}
cls = allNativeProtocolProxies.get(objcClassName);
if (cls != null) {
return getByType(cls);
}
cls = allCustomClasses.get(objcClassName);
if (cls != null) {
return getByType(cls);
}
return null;
}
public static ObjCClass getFromObject(ObjCObject id) {
long handle = id.getHandle();
ObjCClass c = null;
if (handle != 0L) {
long classPtr = ObjCRuntime.object_getClass(handle);
c = getPeerObject(classPtr);
}
if (c != null) {
return c;
}
return getByType(id.getClass());
}
public static ObjCClass getFromObject(long handle) {
long classPtr = ObjCRuntime.object_getClass(handle);
return toObjCClass(classPtr);
}
public static ObjCClass getByType(Class extends ObjCObject> type) {
if (type == null) {
throw new NullPointerException("type");
}
synchronized (objcBridgeLock) {
ObjCClass c = typeToClass.get(type);
if (c == null) {
String name = null;
NativeClass nativeClassAnno = type.getAnnotation(NativeClass.class);
if (nativeClassAnno != null) {
name = nativeClassAnno.value();
name = "".equals(name) ? type.getSimpleName() : name;
long classPtr = ObjCRuntime.objc_getClass(VM.getStringUTFChars(name));
if (classPtr != 0L) {
c = new ObjCClass(classPtr, type, name, false, false);
}
} else {
NativeProtocolProxy nativeProtocolProxyAnno = type.getAnnotation(NativeProtocolProxy.class);
if (nativeProtocolProxyAnno != null) {
name = nativeProtocolProxyAnno.value();
name = "".equals(name) ? type.getSimpleName() : name;
long protocolPtr = ObjCRuntime.objc_getProtocol(VM.getStringUTFChars(name));
if (protocolPtr != 0L) {
c = new ObjCClass(protocolPtr, type, name, false, true);
}
} else {
name = getCustomClassName(type);
c = register(type, name);
}
}
if (c == null) {
throw new ObjCClassNotFoundException(name);
}
typeToClass.put(type, c);
nameToClass.put(name, c);
}
return c;
}
}
@SuppressWarnings("unchecked")
private static List getProtocols(long handle, boolean isProtocol) {
final long protocols = isProtocol ? ObjCRuntime.protocol_copyProtocolList(handle, 0)
: ObjCRuntime.class_copyProtocolList(handle, 0);
if (protocols == 0) {
return (List) Collections.EMPTY_LIST;
}
ArrayList names = new ArrayList<>();
for (long protos = protocols; VM.getPointer(protos) != 0; protos += Bro.IS_64BIT ? 8 : 4) {
long protocol = VM.getPointer(protocols);
names.add(VM.newStringUTF(ObjCRuntime.protocol_getName(protocol)));
}
for (long protos = protocols; VM.getPointer(protos) != 0; protos += Bro.IS_64BIT ? 8 : 4) {
long protocol = VM.getPointer(protocols);
names.addAll(getProtocols(protocol, true));
}
VM.free(protocols);
return names;
}
public static ObjCClass toObjCClass(final long handle) {
long classPtr = handle;
ObjCClass c = getPeerObject(classPtr);
if (c == null) {
c = getByNameNotLoaded(VM.newStringUTF(ObjCRuntime.class_getName(classPtr)));
}
if (c == null) {
for (String protocol : getProtocols(classPtr, false)) {
Class extends ObjCObject> cls = allNativeProtocolProxies.get(protocol);
if (cls != null) {
c = getByType(cls);
break;
}
}
}
while (c == null && classPtr != 0L) {
classPtr = ObjCRuntime.class_getSuperclass(classPtr);
c = getPeerObject(classPtr);
if (c == null) {
c = getByNameNotLoaded(VM.newStringUTF(ObjCRuntime.class_getName(classPtr)));
if (c == null) {
for (String protocol : getProtocols(classPtr, false)) {
Class extends ObjCObject> cls = allNativeProtocolProxies.get(protocol);
if (cls != null) {
c = getByType(cls);
break;
}
}
}
}
}
if (c == null) {
String name = VM.newStringUTF(ObjCRuntime.class_getName(handle));
throw new ObjCClassNotFoundException("Could not find Java class corresponding to Objective-C class: " + name);
}
return c;
}
public static ObjCClass registerCustomClass(Class extends ObjCObject> type) {
if (type.getAnnotation(NativeClass.class) != null) {
throw new IllegalArgumentException("@NativeClass annotated class " + type.getName()
+ " can not be registered as a custom class");
}
if (type.getAnnotation(NativeProtocolProxy.class) != null) {
throw new IllegalArgumentException("@NativeProtocolProxy annotated class " + type.getName()
+ " can not be registered as a custom class");
}
synchronized (objcBridgeLock) {
ObjCClass c = typeToClass.get(type);
if (c == null) {
String name = getCustomClassName(type);
c = register(type, name);
typeToClass.put(type, c);
nameToClass.put(name, c);
}
return c;
}
}
private static String getCustomClassName(Class extends ObjCObject> type) {
CustomClass customClassAnno = type.getAnnotation(CustomClass.class);
String name = type.getName();
if (customClassAnno != null && customClassAnno.value().length() > 0) {
name = customClassAnno.value();
} else {
name = CUSTOM_CLASS_NAME_PREFIX + name;
}
name = name.replace('.', '_');
return name;
}
@SuppressWarnings("unchecked")
private static ObjCClass register(Class extends ObjCObject> type, String name) {
ObjCClass superclass = getByType((Class extends ObjCObject>) type.getSuperclass());
long handle = ObjCRuntime.objc_allocateClassPair(superclass.getHandle(), VM.getStringUTFChars(name), 0);
if (handle == 0L) {
throw new ObjCClassNotFoundException("Failed to create custom Objective-C class for Java class: " + type);
}
for (Entry entry : getCallbacks(type).entrySet()) {
String selName = entry.getKey();
Method method = entry.getValue();
boolean isClassMethod = method.getParameterTypes()[0] == ObjCClass.class;
if (isClassMethod && method.getDeclaringClass() != type) {
// Java doesn't support overriding static methods so the callback
// method for a class method of a super class cannot be used to route
// calls to a static method in a subclass. There must be a @Method
// annotation on the static method (and thus a @Callback method) in
// the custom class.
continue;
}
Selector selector = Selector.register(selName);
String encoding = null;
TypeEncoding typeEncoding = method.getAnnotation(TypeEncoding.class);
if (typeEncoding != null) {
encoding = typeEncoding.value();
} else {
long methodPtr = isClassMethod
? ObjCRuntime.class_getClassMethod(superclass.getHandle(), selector.getHandle())
: ObjCRuntime.class_getInstanceMethod(superclass.getHandle(), selector.getHandle());
if (methodPtr != 0L) {
long encodingPtr = ObjCRuntime.method_getTypeEncoding(methodPtr);
if (encodingPtr != 0L) {
encoding = VM.newStringUTF(encodingPtr);
}
}
}
long impl = VM.getCallbackMethodImpl(method);
// For class methods we need to add the method to the meta-class of the class we are creating
long ownerHandle = isClassMethod ? ObjCRuntime.object_getClass(handle) : handle;
if (!ObjCRuntime.class_addMethod(ownerHandle, selector.getHandle(), impl, encoding != null ? VM.getStringUTFChars(encoding) : 0L)) {
throw new ObjCClassNotFoundException("Failed to add method " + selName + " to custom Objective-C class for Java class: " + type);
}
}
ObjCObject.ObjectOwnershipHelper.registerClass(handle);
ObjCRuntime.objc_registerClassPair(handle);
return new ObjCClass(handle, type, name, !isObjCProxy(type), false);
}
private static Map getCallbacks(Class> type) {
Map callbacks = new HashMap();
findCallbacks(type, callbacks);
return callbacks;
}
private static void findCallbacks(Class> type, Map result) {
for (Method m : type.getDeclaredMethods()) {
if (m.getAnnotation(Callback.class) != null) {
BindSelector bindSelector = m.getAnnotation(BindSelector.class);
if (bindSelector != null) {
if (!result.containsKey(bindSelector.value())) {
result.put(bindSelector.value(), m);
}
}
}
}
}
}