/* Copyright (c) 2007-2010 Timothy Wall & Mark Allerton, All Rights Reserved
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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.
*/
package com.sun.jna;
/**
* TypeMappingCallbackProxy is an implementation of CallbackProxy that handles argument
* and return type marshalling in the same way as done for implementations of Callback.
* The abstract method typeMappedCallback will be invoked with the mapped arguments.
*
* This is intended for use by dynamic languages where JNA's standard marshalling
* for structures etc makes sense.
*/
public abstract class TypeMappingCallbackProxy implements CallbackProxy {
private Class returnType;
private Class[] parameterTypes;
private ToNativeConverter toNative;
private FromNativeConverter[] fromNative;
public TypeMappingCallbackProxy(Class returnType, Class[] parameterTypes, TypeMapper mapper) {
this.parameterTypes = parameterTypes;
this.returnType = returnType;
fromNative = new FromNativeConverter[parameterTypes.length];
if (NativeMapped.class.isAssignableFrom(returnType)) {
toNative = NativeMappedConverter.getInstance(returnType);
} else if (mapper != null) {
toNative = mapper.getToNativeConverter(returnType);
}
for (int i = 0; i < fromNative.length; i++) {
if (NativeMapped.class.isAssignableFrom(parameterTypes[i])) {
fromNative[i] = new NativeMappedConverter(parameterTypes[i]);
} else if (mapper != null) {
fromNative[i] = mapper.getFromNativeConverter(parameterTypes[i]);
}
}
}
/**
* Subclasses should override typeMappedCallback rather than callback - this
* method will be invoked with the mapped arguments, and its return value will also
* be marshalled using the type mapper.
* @param args, mapped using the type mapper
* @return return value
*/
public abstract Object typeMappedCallback(Object[] args);
/**
* Called from native code. All arguments are in an array of
* Object as the first argument. Converts all arguments to types
* required by the actual callback method signature, and converts
* the result back into an appropriate native type.
* This method <em>must not</em> throw exceptions.
*/
public Object callback(Object[] args) {
try {
Object[] callbackArgs = new Object[args.length];
// convert basic supported types to appropriate Java parameter types
for (int i = 0; i < args.length; i++) {
Class type = parameterTypes[i];
Object arg = args[i];
if (fromNative[i] != null) {
FromNativeContext context = null;
// TODO: need to work out the best way to do contexts
// new CallbackParameterContext(type, callbackMethod, args, i);
callbackArgs[i] = fromNative[i].fromNative(arg, context);
} else {
callbackArgs[i] = convertArgument(arg, type);
}
}
Object result = null;
result = convertResult(typeMappedCallback(callbackArgs));
// Synch any structure arguments back to native memory
for (int i = 0; i < callbackArgs.length; i++) {
if (callbackArgs[i] instanceof Structure
&& !(callbackArgs[i] instanceof Structure.ByValue)) {
((Structure) callbackArgs[i]).autoWrite();
}
}
return result;
}
catch (Throwable t) {
handleUncaughtException(t);
return null;
}
}
/**
* This implementation returns the parameter types supplied to the constructor
* @return the classes of the callback parameters
*/
public Class[] getParameterTypes() {
return (Class[]) parameterTypes.clone();
}
/**
* This implementation returns the return type supplied to the constructor
* @return the return type class
*/
public Class getReturnType() {
return returnType;
}
/**
* Convert argument from its basic native type to the given
* Java parameter type.
*/
private Object convertArgument(Object value, Class dstType) {
if (value instanceof Pointer) {
if (dstType == String.class) {
value = ((Pointer) value).getString(0);
} else if (dstType == WString.class) {
value = new WString(((Pointer) value).getString(0, true));
} else if (dstType == String[].class
|| dstType == WString[].class) {
value = ((Pointer) value).getStringArray(0, dstType == WString[].class);
} else if (Callback.class.isAssignableFrom(dstType)) {
value = CallbackReference.getCallback(dstType, (Pointer) value);
} else if (Structure.class.isAssignableFrom(dstType)) {
Structure s = Structure.newInstance(dstType);
// If passed by value, don't hold onto the pointer, which
// is only valid for the duration of the callback call
if (Structure.ByValue.class.isAssignableFrom(dstType)) {
byte[] buf = new byte[s.size()];
((Pointer) value).read(0, buf, 0, buf.length);
s.getPointer().write(0, buf, 0, buf.length);
} else {
s.useMemory((Pointer) value);
}
s.read();
value = s;
}
} else if ((boolean.class == dstType || Boolean.class == dstType)
&& value instanceof Number) {
value = Function.valueOf(((Number) value).intValue() != 0);
}
return value;
}
private Object convertResult(Object value) {
if (toNative != null) {
value = toNative.toNative(value, null); //new CallbackResultContext(callbackMethod));
}
if (value == null)
return null;
Class cls = value.getClass();
if (Structure.class.isAssignableFrom(cls)) {
if (Structure.ByValue.class.isAssignableFrom(cls)) {
return value;
}
return ((Structure) value).getPointer();
} else if (cls == boolean.class || cls == Boolean.class) {
return Boolean.TRUE.equals(value) ?
Function.INTEGER_TRUE : Function.INTEGER_FALSE;
} else if (cls == String.class || cls == WString.class) {
return CallbackReference.getNativeString(value, cls == WString.class);
} else if (cls == String[].class || cls == WString.class) {
StringArray sa = cls == String[].class
? new StringArray((String[]) value)
: new StringArray((WString[]) value);
// Delay GC until array itself is GC'd.
CallbackReference.allocations.put(value, sa);
return sa;
} else if (Callback.class.isAssignableFrom(cls)) {
return CallbackReference.getFunctionPointer((Callback) value);
}
return value;
}
/**
* Subclasses that hold an actual Callback reference may override this method
* to call Native.getCallbackExceptionHandler().uncaughtException directly with
* the callback.
* @param e
*/
protected void handleUncaughtException(Throwable e) {
// This is to pacify the existing exception handlers - which expect a Callback
Callback cb = new Callback() {
public String toString() {
return TypeMappingCallbackProxy.this.toString();
}
};
Native.getCallbackExceptionHandler().uncaughtException(cb, e);
}
}