All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.weblite.objc.NSObject Maven / Gradle / Ivy

There is a newer version: 1.2
Show newest version
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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy