org.rococoa.internal.OCInvocationCallbacks Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2007, 2008 Duncan McGregor
*
* This file is part of Rococoa, a library to allow Java to talk to Cocoa.
*
* Rococoa is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Rococoa is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Rococoa. If not, see .
*/
package org.rococoa.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.rococoa.ID;
import org.rococoa.Rococoa;
import org.rococoa.RococoaException;
import org.rococoa.cocoa.foundation.NSInvocation;
import org.rococoa.cocoa.foundation.NSMethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jna.Memory;
/**
* Holds the callbacks called when a method is invoked on an Objective-C proxy
* for a Java object.
*
* When a message is sent to an OC object first it is sent
* methodSignatureForSelector: Our Obj-C proxy forwards this to
* methodSignatureCallback; we build a method signature string in Java
* corresponding to the Java method and return it.
*
* The object is then sent forwardInvocation: passing an NSInvocation. It
* forwards this to selectorInvokedCallback, which we use to invoke the method
* on the Java Object.
*
* @author duncan
*
*/
@SuppressWarnings("nls")
public class OCInvocationCallbacks {
private static Logger logging = LoggerFactory.getLogger("org.rococoa.callback");
private final Object javaObject;
/**
* Called when method is about to be invoked on OC proxy and needs a method signature as String
*
* @see "http://www.cocoadev.com/index.pl?NSMethodSignature"
*/
public final RococoaLibrary.MethodSignatureCallback methodSignatureCallback =
new RococoaLibrary.MethodSignatureCallback() {
public String callback(String selectorName) {
if (logging.isTraceEnabled()) {
logging.trace("callback wanting methodSignature for selector {}", selectorName);
}
return methodSignatureForSelector(selectorName);
}
};
/**
* Called when method has been invoked on OC proxy and needs to be forwarded to javaObject
*/
public final RococoaLibrary.SelectorInvokedCallback selectorInvokedCallback =
new RococoaLibrary.SelectorInvokedCallback() {
public void callback(String selectorName, ID nsInvocation) {
if (logging.isTraceEnabled()) {
logging.trace("callback invoking {} on {}", selectorName, javaObject);
}
callMethod(javaObject, selectorName, Rococoa.wrap(nsInvocation, NSInvocation.class));
}
};
public OCInvocationCallbacks(Object javaObject) {
this.javaObject = javaObject;
}
protected String methodSignatureForSelector(String selectorName) {
Method method = methodForSelector(selectorName);
return method == null ?
null :
ocMethodSignatureAsString(method);
}
protected Method methodForSelector(String selectorName) {
if (null == selectorName) {
logging.error("methodForSelector called with null selectorName");
return null;
}
int parameterCount = countColons(selectorName);
String methodName = methodNameForSelector(selectorName);
try {
Method[] methods = javaObject.getClass().getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.getParameterTypes().length == parameterCount)
return method;
}
logging.debug("No method for selector:" + selectorName);
return null;
} catch (Exception e) {
logging.error("Exception finding methodForSelector", e);
return null;
}
}
/**
* @see "http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_13_section_9.html"
*/
protected String ocMethodSignatureAsString(Method method) {
StringBuilder result = new StringBuilder();
result.append(stringForType(method.getReturnType()));
result.append("@:"); // self and cmd - id and selector
for (Class> parameterType : method.getParameterTypes()) {
result.append(stringForType(parameterType));
}
return result.toString();
}
private void callMethod(Object o, String selectorName, NSInvocation invocation) {
try {
Method method = methodForSelector(selectorName);
NSMethodSignature nsMethodSignature = invocation.methodSignature();
String typeToReturnToObjC = nsMethodSignature.methodReturnType();
if (nsMethodSignature.numberOfArguments() - method.getParameterTypes().length != 2) // self, _cmd
throw new NoSuchMethodException(String.format(
"Number of arguments mismatch for invocation of selector %s (%s arguments supplied), method %s expects %s",
selectorName, nsMethodSignature.numberOfArguments(), method.getName(), method.getParameterTypes().length));
if (typeToReturnToObjC.equals("v") && method.getReturnType() != void.class)
throw new NoSuchMethodException(String.format(
"Selector %s expects void return, but method %s returns %s",
selectorName, method.getName(), method.getReturnType()));
if (method.getReturnType() == void.class && !(typeToReturnToObjC.equals("v")))
throw new NoSuchMethodException(String.format(
"Method %s returns void, but selector %s expects %s",
method.getName(), selectorName, typeToReturnToObjC));
Object[] marshalledArgs = argsForFrom(method, invocation, nsMethodSignature);
method.setAccessible(true); // needed if implementation is an anonymous subclass of Object
Object result = method.invoke(o, marshalledArgs);
putResultIntoInvocation(invocation, typeToReturnToObjC, result);
} catch (InvocationTargetException e) {
logging.error("Exception calling method for selector " + selectorName, e);
throw new RococoaException("Exception calling method for selector " + selectorName, e.getCause());
} catch (Exception e) {
logging.error("Exception calling method for selector " + selectorName, e);
throw new RococoaException("Exception calling method for selector " + selectorName, e);
}
}
private String methodNameForSelector(String selectorName) {
String candidate = selectorName.replaceAll(":", "_");
return candidate.endsWith("_") ?
candidate.substring(0, candidate.length() - 1) :
candidate;
}
private Object[] argsForFrom(Method method, NSInvocation invocation, NSMethodSignature nsMethodSignature) {
Class>[] parameterTypes = method.getParameterTypes();
Object[] result = new Object[parameterTypes.length];
for (int i = 0; i < result.length; i++) {
int indexAccountingForSelfAndCmd = 2 + i;
result[i] = javaObjectForOCArgument(invocation,
indexAccountingForSelfAndCmd,
nsMethodSignature.getArgumentTypeAtIndex(indexAccountingForSelfAndCmd),
parameterTypes[i]);
}
return result;
}
/**
* At this point we have an NSInvocation, which has the arguments to the
* call in it. We know the type of the argument, and the type of the
* parameter expected by the Java method. [1]
* Our mission is to get the value of the argument from the NSInvocation
* and return a Java object of the desired type.
*/
private Object javaObjectForOCArgument(NSInvocation invocation,
int indexInInvocation, String objCArgumentTypeAsString, Class> javaParameterType) {
NSInvocationMapper mapper = NSInvocationMapperLookup.mapperForType(javaParameterType);
if (mapper == null)
throw new IllegalStateException(
String.format("Don't (yet) know how to marshall argument Objective-C type %s as %s",
objCArgumentTypeAsString, javaParameterType));
return mapper.readArgumentFrom(invocation, indexInInvocation, javaParameterType);
}
private void putResultIntoInvocation(NSInvocation invocation, String typeToReturnToObjC, Object result) {
if (typeToReturnToObjC.equals("v")) {
if (result != null)
throw new IllegalStateException("Java method returned a result, but expected void");// void
return;
}
if (null == result) {
return;
}
Memory buffer = bufferForReturn(typeToReturnToObjC, result);
if (buffer == null)
throw new IllegalStateException(
String.format("Don't (yet) know how to marshall result %s as Objective-C type %s", result, typeToReturnToObjC));
invocation.setReturnValue(buffer);
}
private Memory bufferForReturn(String typeToReturnToObjC, Object methodCallResult) {
NSInvocationMapper mapper = NSInvocationMapperLookup.mapperForType(methodCallResult.getClass());
return mapper == null ? null : mapper.bufferForResult(methodCallResult);
}
private int countColons(String selectorName) {
int result = 0;
for (int i = 0; i < selectorName.length(); i++) {
if (selectorName.charAt(i) == ':')
result++;
}
return result;
}
private String stringForType(Class> clas) {
NSInvocationMapper result = NSInvocationMapperLookup.mapperForType(clas);
if (result == null)
throw new RococoaException("Unable to give Objective-C type string for Java type " + clas);
return result.typeString();
}
/*
* [1] - http://en.wikipedia.org/wiki/Parameter_(computer_science)
* Although parameters are also commonly referred to as arguments,
* arguments are more properly thought of as the actual values or references
* assigned to the parameter variables when the subroutine is called at
* runtime. When discussing code that is calling into a subroutine, any
* values or references passed into the subroutine are the arguments, and
* the place in the code where these values or references are given is the
* parameter list. When discussing the code inside the subroutine
* definition, the variables in the subroutine's parameter list are the
* parameters, while the values of the parameters at runtime are the
* arguments.
*/
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy