ca.weblite.objc.NSObject Maven / Gradle / Ivy
Show all versions of java-objc-bridge Show documentation
package ca.weblite.objc;
import static ca.weblite.objc.RuntimeUtils.cls;
import static ca.weblite.objc.RuntimeUtils.msg;
import static ca.weblite.objc.RuntimeUtils.msgPointer;
import static ca.weblite.objc.RuntimeUtils.sel;
import static ca.weblite.objc.RuntimeUtils.selName;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sun.jna.Function;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.DoubleByReference;
import com.sun.jna.ptr.LongByReference;
import com.sun.jna.ptr.PointerByReference;
import ca.weblite.objc.annotations.Msg;
import ca.weblite.objc.jna.PointerTool;
/**
* The base class for objects that can interact with the Objective-C runtime.
* NSObjects are connected to both an Objective-C peer object, and an Objective-C
* parent object. The peer is a reflection of the object in Objective-C. It is
* a WLProxy object that will simply forward messages from Objective-C to Java.
*
* The parent object is used as a sort of superclass so that messages that aren't
* explicitly handled by the Java class can be handled by the superclass.
*
* Simple Example
*
* The following example shows a subclass of NSObject that is used as a delegate
* for an NSOpenPanel. Notice, that, by using the {@literal @}Msg annotation, the
* start() method is effectively called via Objective-C. Similarly, the panelSelectionDidChange()
* method is called by the NSOpenPanel class to respond to events when the user clicks on
* a different item in the open dialog.
*
*
* If you run this application, it will open an NSOpenPanel modal dialog and allow you to
* select a file. If you run this program and select a single file, the output will look
* something like:
*
*
*
* see NSOpenPanelSample
* @author shannah
* @version $Id: $Id
* @since 1.1
*/
public class NSObject extends Proxy implements PeerableRecipient {
/**
* Pointer to the parent objective-c object of this object.
*/
public Pointer parent;
/**
* Pointer to the objective-c class of the parent object.
*/
private Pointer cls;
/**
* Maps string selectors to java methods for this class.
*/
private static final Map, Map> methodMap = new HashMap<>();
/**
* Returns the method map for a particular class. The Map that is returned maps string selectors
* to Method objects.
*
* @param cls The class whose map we wish to obtain
* @return The map that maps string selectors
*/
protected static Map getMethodMap(Class> cls){
Map mm = methodMap.get(cls);
if ( mm == null ){
mm = new HashMap();
// acquire all possible superclasses
final List> classes = new ArrayList<>();
classes.add(cls);
Class> superclass = cls.getSuperclass();
while (superclass != null) {
classes.add(superclass);
superclass = superclass.getSuperclass();
}
for (Class> c : classes) {
Method[] methods = c.getDeclaredMethods();
for ( int i=0; iConstructor for NSObject.
*
* @param className a {@link java.lang.String} object.
*/
public NSObject(String className){
this();
init(className);
}
/**
* Creates null proxy (i.e. a proxy around a null pointer). In order
* to make this class functional and register it with the objective-c
* runtime, you still need to call one of the init() method variants.
*/
public NSObject(){
super();
}
/**
* Creates an NSObject to wrap (i.e. send messages to) the specified
* Objective-C object. This doesn't actually register an object yet
* with the Objective-C runtime. You must still call init() to do this.
*
* @param peer a {@link com.sun.jna.Pointer} object.
*/
public NSObject(Pointer peer){
super(peer);
}
/**
* Creates a null proxy using the specified client as the default client
* with which to send messages to the objective-c runtime.
*
* @param c The client that should be used to send messages in this
* object.
*/
public NSObject(Client c){
super(c);
}
/**
* Creates a proxy for the specified objective-c object.
*
* @param c The client that should be used for sending messages via this proxy.
* @param peer The peer object.
*/
public NSObject(Client c, Pointer peer){
super(c, peer);
}
/**
* Initializes this object and registers it with the Objective-C runtime.
*
* @return Self for chaining.
* @param parent a {@link com.sun.jna.Pointer} object.
*/
public NSObject init(Pointer parent){
this.cls = Runtime.INSTANCE.object_getClass(parent);
this.parent = parent;
if ( this.peer == Pointer.NULL ){
this.peer = new Pointer(RuntimeUtils.createProxy(this));
}
return this;
}
/**
* Initializes this object and registers it with the Objective-C runtime.
*
* @param cls The name of the class to use as the super class for this object.
* @return Self for chaining.
*/
public NSObject init(String cls){
Pointer res = Client.getRawClient().sendPointer(cls, "alloc");
Client.getRawClient().sendPointer(res, "init");
return init(res);
}
/*
@Msg(selector="valueForKey:", like="NSObject.valueForKey:")
public Object valueForKey(String key){
try {
Field fld = this.getClass().getField(key);
return fld.get(this);
} catch (Exception ex) {
}
Pointer sig = this.methodSignatureForSelector(sel("valueForKey:"));
Proxy invocation = Client.getInstance().sendProxy("NSInvocation", "invocationWithMethodSignature:", sig);
invocation.send("setTarget:", parent);
Pointer nsKey = str(key);
invocation.send("setArgument:AtIndex:", nsKey, 2);
this.forwardInvocationToParent(invocation.getPeer());
Pointer p = new PointerByReference().getPointer();
invocation.send("getReturnValue:", p );
}
*/
/**
* Returns the java method that responds to a specific selector for the
* current object.
*
* @param selector The
* @return The method object that handles the specified selector (or null
* if none is specified).
* @see RuntimeUtils#sel(String)
*/
public Method methodForSelector(String selector){
return getMethodMap(this.getClass()).get(selector);
}
/**
* Returns the NSMethodSignature (Objective-C) object pointer for the
* specified selector. If there is a Java method registered with this
* selector, then it will return its signature. Otherwise it will
* return the method signature of the parent object.
*
* @param selector a {@link com.sun.jna.Pointer} object.
* @return Pointer to an NSMethodSignature object.
* @see NSMethodSignature Class Reference
*/
public Pointer methodSignatureForSelector(Pointer selector){
long res = methodSignatureForSelector(PointerTool.getPeer(selector));
return new Pointer(res);
}
/**
* {@inheritDoc}
*
* Returns the NSMethodSignature (Objective-C) object pointer for the
* specified selector. If there is a Java method registered with this
* selector, then it will return its signature. Otherwise it will
* return the method signature of the parent object.
* @see NSMethodSignature Class Reference
*/
@Override
public long methodSignatureForSelector(long lselector) {
Pointer selector = new Pointer(lselector);
Method method = methodForSelector(selName(selector));
if ( method != null){
Msg message = method.getAnnotation(Msg.class);
if ( !"".equals(message.signature()) ){
long res = PointerTool.getPeer(
msgPointer(cls("NSMethodSignature"), "signatureWithObjCTypes:", message.signature())
);
return res;
} else if ( !"".equals(message.like())){
String[] parts = message.like().split("\\.");
Proxy instance = client.chain(parts[0], "alloc").chain("init");
Pointer out = msgPointer(instance.getPeer(), "methodSignatureForSelector:", sel(parts[1]));
return PointerTool.getPeer(out);
}
}
return PointerTool.getPeer(msgPointer(parent, "methodSignatureForSelector:", selector));
}
/**
* Forwards an NSInvocation to the parent object to be handled. The parent will
* handle the invocation (if it contains an appropriate selector), but the peer
* will still be treated as the "Self" of the message. I.e. this acts exactly
* like calling super() in an OO language.
*
* @param invocation Pointer to the objective-c NSInvocation object.
* @see NSInvocation Class Reference
*/
public void forwardInvocationToParent(Pointer invocation){
forwardInvocationToParent(PointerTool.getPeer(invocation));
}
/**
* Forwards an NSInvocation to the parent object to be handled. The parent will
* handle the invocation (if it contains an appropriate selector), but the peer
* will still be treated as the "Self" of the message. I.e. this acts exactly
* like calling super() in an OO language.
*
* @see NSInvocation Class Reference
* @param linvocation a long.
*/
public void forwardInvocationToParent(long linvocation){
Pointer invocation = new Pointer(linvocation);
Client rawClient = Client.getRawClient();
Pointer sig = msgPointer(invocation, "methodSignature");
Proxy pSig = new Proxy(rawClient, sig);
Pointer selector = msgPointer(invocation, "selector");
long numArgs = (Long)pSig.send("numberOfArguments");
long respondsToSelector = msg(parent, "respondsToSelector:", selector );
if ( respondsToSelector > 0 ){
long impl = msg(parent, "methodForSelector:", selector);
Pointer pImpl = new Pointer(impl);
Function func = Function.getFunction(pImpl);
long returnType = (Long)pSig.send("methodReturnType");
String strReturnType = new Pointer(returnType).getString(0);
String prefixes = "rnNoORV";
int offset = 0;
while ( prefixes.indexOf(strReturnType.charAt(offset)) != -1 ){
offset++;
if ( offset > strReturnType.length()-1 ){
break;
}
}
if ( offset > 0 ){
strReturnType = strReturnType.substring(offset);
}
Object[] args = new Object[(int) numArgs];
args[0] = peer;
args[1] = parent;
for ( int i=2; i retType = null;
switch ( retTypeChar){
case 'v':
retType = void.class; break;
case 'f':
retType = float.class; break;
case 'd':
retType = double.class; break;
case '*':
retType = String.class; break;
case 'i':
case 'I':
case 's':
case 'S':
case 'c':
case 'C':
case 'B':
retType = int.class;break;
case 'l':
case 'L':
case 'q':
case 'Q':
retType = long.class;break;
case '@':
case '#':
case ':':
case '^':
case '?':
retType = Pointer.class; break;
default:
// If we don't know how to handle the return type properly,
// then let's just give up and pass it to the parent object
// the normal way
//System.out.println("We give up... passing "+sel(selector)+" to parent");
msg(invocation, "invokeWithTarget:", parent);
return;
}
Object retVal = func.invoke(retType, args);
if ( !void.class.equals(retType)){
// We need to set the return value.
if ( retVal == null ){
retVal = 0L;
}
Pointer retValRef = RuntimeUtils.getAsReference(retVal, strReturnType);
msg(invocation, "setReturnValue:", retValRef );
}
} else {
throw new RuntimeException("Object does not handle selector "+selName(selector));
}
}
/**
* Handles a method invocation. This will first check to see if there is a matching
* Java method in this class (method requires the @Msg annotation), and call that
* method if it is available. Otherwise it will obtain the method implementation from
* the parent class and execute it. The return value is added to the NSInvocation object.
*
* This method is used by the Native WLProxy to pipe all messages to this object's peer
* through Java so that it has a chance to process it.
*
* @param invocation NSInvocation Objective-C object that is to be invoked.
* @see NSInvocation Class Reference
* @see NSProxy forwardInvocation Documentation
*/
public void forwardInvocation(Pointer invocation){
forwardInvocation(PointerTool.getPeer(invocation));
}
/**
* {@inheritDoc}
*
* Handles a method invocation. This will first check to see if there is a matching
* Java method in this class (method requires the @Msg annotation), and call that
* method if it is available. Otherwise it will obtain the method implementation from
* the parent class and execute it. The return value is added to the NSInvocation object.
*
* This method is used by the Native WLProxy to pipe all messages to this object's peer
* through Java so that it has a chance to process it.
* @see NSInvocation Class Reference
* @see NSProxy forwardInvocation Documentation
*
* @throws NSMessageInvocationException If an exception occurs while attempting to invoke the method.
*/
@Override
public void forwardInvocation(long linvocation) {
Pointer invocation = new Pointer(linvocation);
Client rawClient = Client.getRawClient();
Pointer sig = msgPointer(invocation, "methodSignature");
Proxy pSig = new Proxy(rawClient, sig);
Pointer selector = msgPointer(invocation, "selector");
long numArgs = (Long)pSig.send("numberOfArguments");
String selName = selName(selector);
Method method = methodForSelector(selName);
if ( method != null){
// Perform the method and provide the correct output for the invocation
Object[] args = new Object[(int) numArgs - 2];
for ( int i=2; iObjective-C selectors reference
* @return a boolean.
*/
public boolean respondsToSelector(Pointer selector){
return respondsToSelector(PointerTool.getPeer(selector));
}
/**
* {@inheritDoc}
*
* Checks whether this object responds to the given selector. This is used
* by the WLProxy (Objective-C peer object) to route requests for its NSProxy
* respondsToSelector: message. This will check to see if there is a registered
* java method in the class that responds to the selector (based on the @Msg
* annotation). Then it will check the parent object to see if it responds
* to the selector.
* @see RuntimeUtils#sel(Peerable)
* @see Objective-C selectors reference
*/
@Override
public boolean respondsToSelector(long lselector) {
Pointer selector = new Pointer(lselector);
Method method = methodForSelector(selName(selector));
if ( method != null){
return true;
}
return (msg(parent, "respondsToSelector:", selector ) > 0);
}
/** {@inheritDoc} */
@Override
public NSObject chain(Pointer selector, Object... args){
return (NSObject)super.chain(selector, args);
}
/** {@inheritDoc} */
@Override
public NSObject chain(String selector, Object... args){
return (NSObject)super.chain(selector, args);
}
/** {@inheritDoc} */
@Override
public NSObject chain(Message... msgs){
return (NSObject)super.chain(msgs);
}
/**
* dealloc.
*
* @return a {@link ca.weblite.objc.NSObject} object.
*/
public NSObject dealloc(){
this.send("dealloc");
return this;
}
}