Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[467] direct enum type mapping #468

Merged
merged 9 commits into from
Jul 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
NOTE: as of JNA 4.0, JNA is now dual-licensed under LGPL and ASL (see LICENSE).
NOTE: as of JNA 4.0, JNA is now dual-licensed under LGPL and ASL (see LICENSE).

NOTE: JNI native support is typically incompatible between minor versions, and almost always incompatible between major versions.

Expand All @@ -15,12 +15,12 @@ Features
* Updated AIX natives and build - [@twall](https://github.com/twall).
* [#290](https://github.com/twall/jna/pull/290): Improved the stacktrace for the exceptions thrown by `com.sun.jna.Structure` - [@ebourg](https://github.com/ebourg).
* [#332](https://github.com/twall/jna/pull/332): Added Win32 Monitor Configuration API in `com.sun.jna.platform.win32.Dxva2` - [@msteiger](https://github.com/msteiger).
* Added Winspool monitor sample and updated Kernel32, WinBase and Winspool - [@wolftobias](https://github.com/wolftobias).
* [#333](https://github.com/twall/jna/pull/333): Added `com.sun.jna.platform.win32.Ole32.CoTaskMemAlloc`, `CoTaskMemRealloc` and `CoTaskMemFree` - [@msteiger](https://github.com/msteiger).
* Added Winspool monitor sample and updated Kernel32, WinBase, Winspool - [@wolftobias](https://github.com/wolftobias).
* [#333](https://github.com/twall/jna/pull/333): Added `CoTaskMemAlloc`, `CoTaskMemRealloc` and `CoTaskMemFree` to `com.sun.jna.platform.win32.Ole32` - [@msteiger](https://github.com/msteiger).
* [#334](https://github.com/twall/jna/pull/334): Added `com.sun.jna.platform.win32.Shell32.SHGetKnownFolderPath` and `KnownFolders` GUID constants - [@msteiger](https://github.com/msteiger).
* [#338](https://github.com/twall/jna/pull/338): Added `com.sun.jna.platform.mac.XAttr` and `com.sun.jna.platform.mac.XAttrUtil` JNA wrapper for `<sys/xattr.h>` for Mac OS X - [@rednoah](https://github.com/rednoah).
* [#339](https://github.com/twall/jna/pull/339): Added `com.sun.jna.platform.win32.User32.GetWindowPlacement`, `SetWindowPlacement`, `AdjustWindowRect`, `AdjustWindowRectEx`, `ExitWindowsEx`, and `LockWorkstation` - [@Timeroot](https://github.com/Timeroot).
* [#286](https://github.com/twall/jna/pull/286): Added `com.sun.jna.platform.win32.Kernel32.CreateRemoteThread`, `WritePocessMemory` and `ReadProcessMemory` - [@sstokic-tgm](https://github.com/sstokic-tgm).
* [#339](https://github.com/twall/jna/pull/339): Added `GetWindowPlacement`, `SetWindowPlacement`, `AdjustWindowRect`, `AdjustWindowRectEx`, `ExitWindowsEx`, and `LockWorkstation` to `com.sun.jna.platform.win32.User32` - [@Timeroot](https://github.com/Timeroot).
* [#286](https://github.com/twall/jna/pull/286): Added `CreateRemoteThread`, `WritePocessMemory` and `ReadProcessMemory` to `com.sun.jna.platform.win32.Kernel32` - [@sstokic-tgm](https://github.com/sstokic-tgm).
* [#350](https://github.com/twall/jna/pull/350): Added `jnacontrib.x11.api.X.Window.getSubwindows` - [@rm5248](https://github.com/rm5248).
* Improved `contrib/msoffice` sample - [@wolftobias](https://github.com/wolftobias).
* [#352](https://github.com/twall/jna/pull/352): Performance improvements due to reduced locking in `com.sun.jna.Library$Handler` and fewer vararg checks in `com.sun.jna.Function` - [@Boereck](https://github.com/Boereck).
Expand Down Expand Up @@ -67,6 +67,7 @@ Bug Fixes
* [#403](https://github.com/twall/jna/pull/403): Fix `com.sun.jna.platform.win32.COM.COMUtils.SUCCEEDED` and `FAILED` - [@lwahonen](https://github.com/lwahonen).
* [#404](https://github.com/twall/jna/pull/404): Fix `VARIANT` constructors for `int`, `short`, and `long` - [@lwahonen](https://github.com/lwahonen).
* [#420](https://github.com/twall/jna/pull/420): Fix structure leaving always one element in ThreadLocal set - [@sjappig](https://github.com/sjappig).
* [#467](https://github.com/twall/jna/issues/467): Fix TypeMapper usage with direct-mapped libraries converting primitives to Java objects (specifically enums) - [@twall](https://github.com/twall).

Release 4.1
===========
Expand Down
34 changes: 24 additions & 10 deletions native/dispatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -1139,20 +1139,31 @@ toNativeTypeMapped(JNIEnv* env, jobject obj, void* valuep, size_t size, jobject
}

static void
fromNativeTypeMapped(JNIEnv* env, jobject from_native, void* resp, ffi_type* type, jclass javaClass, void* result) {
int jtype = get_jtype_from_ffi_type(type);
jobject value = new_object(env, (char)jtype, resp, JNI_TRUE);
fromNativeTypeMapped(JNIEnv* env, jobject from_native,
void* native_return_value,
ffi_type* native_return_type,
jclass java_return_class,
void* result_storage) {
int jtype = get_jtype_from_ffi_type(native_return_type);
jobject value = new_object(env, (char)jtype, native_return_value, JNI_TRUE);
if (!(*env)->ExceptionCheck(env)) {
jobject obj = (*env)->CallStaticObjectMethod(env, classNative,
MID_Native_fromNativeTypeMapped,
from_native, value, javaClass);
from_native, value, java_return_class);
if (!(*env)->ExceptionCheck(env)) {
// Must extract primitive types
if (type->type != FFI_TYPE_POINTER) {
extract_value(env, obj, result, type->size, JNI_TRUE);
// Convert objects into primitive types if the return class demands it
if ((*env)->IsSameObject(env, java_return_class, classPrimitiveBoolean)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveByte)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveCharacter)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveShort)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveInteger)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveLong)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveFloat)
|| (*env)->IsSameObject(env, java_return_class, classPrimitiveDouble)) {
extract_value(env, obj, result_storage, native_return_type->size, JNI_TRUE);
}
else {
*(jobject*)result = obj;
*(jobject*)result_storage = obj;
}
}
}
Expand Down Expand Up @@ -1444,6 +1455,7 @@ JNA_init(JNIEnv* env) {
return NULL;
}

/** Copy value from the given Java object into the given storage buffer. */
void
extract_value(JNIEnv* env, jobject value, void* resp, size_t size, jboolean promote) {
if (value == NULL) {
Expand Down Expand Up @@ -1514,8 +1526,9 @@ extract_value(JNIEnv* env, jobject value, void* resp, size_t size, jboolean prom
*(void **)resp = getNativeAddress(env, value);
}
else {
fprintf(stderr, "JNA: unrecognized return type, size %d\n", (int)size);
fprintf(stderr, "JNA: extract_value: unrecognized return type, size %d\n", (int)size);
memset(resp, 0, size);
throwByName(env, EError, "Unrecognized return type");
}
}

Expand Down Expand Up @@ -1780,7 +1793,8 @@ method_handler(ffi_cif* cif, void* volatile resp, void** argp, void *cdata) {
resp = alloca(sizeof(jobject));
}
else if (data->rflag == CVT_TYPE_MAPPER) {
// Ensure enough space for the inner call result
// Ensure enough space for the inner call result, which may differ
// from the closure result
resp = alloca(data->cif.rtype->size);
}
else if (data->rflag == CVT_STRUCTURE_BYVAL) {
Expand Down
12 changes: 6 additions & 6 deletions src/com/sun/jna/Native.java
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,7 @@ public static void register(Class cls, NativeLibrary lib) {
List mlist = new ArrayList();
TypeMapper mapper = (TypeMapper)
lib.getOptions().get(Library.OPTION_TYPE_MAPPER);
cacheOptions(cls, lib.getOptions(), null);

for (int i=0;i < methods.length;i++) {
if ((methods[i].getModifiers() & Modifier.NATIVE) != 0) {
Expand All @@ -1444,7 +1445,10 @@ public static void register(Class cls, NativeLibrary lib) {
throw new IllegalArgumentException(rclass + " is not a supported return type (in method " + method.getName() + " in " + cls + ")");
case CVT_TYPE_MAPPER:
fromNative = mapper.getFromNativeConverter(rclass);
closure_rtype = FFIType.get(rclass).peer;
// FFIType.get() always looks up the native type for any given
// class, so if we actually have conversion into a Java
// object, make sure we use the proper type information
closure_rtype = FFIType.get(rclass.isPrimitive() ? rclass : Pointer.class).peer;
rtype = FFIType.get(fromNative.nativeType()).peer;
break;
case CVT_NATIVE_MAPPED:
Expand Down Expand Up @@ -1490,10 +1494,7 @@ else if (cvt[t] == CVT_TYPE_MAPPER) {
closure_atypes[t] = FFIType.get(Pointer.class).peer;
break;
case CVT_TYPE_MAPPER:
if (type.isPrimitive())
closure_atypes[t] = FFIType.get(type).peer;
else
closure_atypes[t] = FFIType.get(Pointer.class).peer;
closure_atypes[t] = FFIType.get(type.isPrimitive() ? type : Pointer.class).peer;
atypes[t] = FFIType.get(toNative[t].nativeType()).peer;
break;
case CVT_DEFAULT:
Expand Down Expand Up @@ -1535,7 +1536,6 @@ else if (cvt[t] == CVT_TYPE_MAPPER) {
registeredClasses.put(cls, handles);
registeredLibraries.put(cls, lib);
}
cacheOptions(cls, lib.getOptions(), null);
}

/** Take note of options used for a given library mapping, to facilitate
Expand Down
3 changes: 2 additions & 1 deletion src/com/sun/jna/Structure.java
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,7 @@ private void init(Pointer[] els) {
write();
}

/** Obtain a pointer to the native FFI type descriptor for the given object. */
static Pointer get(Object obj) {
if (obj == null)
return FFITypes.ffi_type_pointer;
Expand Down Expand Up @@ -1847,7 +1848,7 @@ private static Pointer get(Object obj, Class cls) {
typeInfoMap.put(obj, type);
return type.getPointer();
}
throw new IllegalArgumentException("Unsupported Structure field type " + cls);
throw new IllegalArgumentException("Unsupported type " + cls);
}
}
}
Expand Down
49 changes: 46 additions & 3 deletions test/com/sun/jna/DirectTypeMapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

public class DirectTypeMapperTest extends TestCase {

/** Converts boolean to int when going to native. */
public static class DirectTestLibraryBoolean {
final static int MAGIC = 0xABEDCF23;
public native int returnInt32Argument(boolean b);
Expand All @@ -37,6 +38,7 @@ public Class nativeType() {
Native.register(NativeLibrary.getInstance("testlib", options));
}
}
/** Converts String to int when going to native. */
public static class DirectTestLibraryString {
public native int returnInt32Argument(String s);
static {
Expand All @@ -54,6 +56,7 @@ public Class nativeType() {
Native.register(NativeLibrary.getInstance("testlib", options));
}
}
/** Converts CharSequence to int when going to native. */
public static class DirectTestLibraryCharSequence {
public native int returnInt32Argument(String n);
static {
Expand All @@ -72,6 +75,7 @@ public Class nativeType() {
Native.register(NativeLibrary.getInstance("testlib", options));
}
}
/** Converts Number to int when going to native. */
public static class DirectTestLibraryNumber {
public native int returnInt32Argument(Number n);
static {
Expand All @@ -90,7 +94,6 @@ public Class nativeType() {
Native.register(NativeLibrary.getInstance("testlib", options));
}
}

public void testBooleanToIntArgumentConversion() {
DirectTestLibraryBoolean lib = new DirectTestLibraryBoolean();
assertEquals("Failed to convert Boolean argument to Int",
Expand All @@ -116,7 +119,8 @@ public void testNumberToIntArgumentConversion() {
assertEquals("Failed to convert Double argument to Int", MAGIC,
lib.returnInt32Argument(new Double(MAGIC)));
}
public static class DirectBooleanTestLibrary {
/** Uses a type mapper to convert boolean->int and int->boolean */
public static class DirectTestLibraryBidirectionalBoolean {
public native boolean returnInt32Argument(boolean b);
static {
final int MAGIC = 0xABEDCF23;
Expand Down Expand Up @@ -144,7 +148,7 @@ public Class nativeType() {
}
}
public void testIntegerToBooleanResultConversion() throws Exception {
DirectBooleanTestLibrary lib = new DirectBooleanTestLibrary();
DirectTestLibraryBidirectionalBoolean lib = new DirectTestLibraryBidirectionalBoolean();
// argument "true" converts to zero; result zero converts to "true"
assertTrue("Failed to convert integer return to boolean TRUE",
lib.returnInt32Argument(true));
Expand Down Expand Up @@ -188,6 +192,45 @@ public void testTypeMapperResultTypeConversion() throws Exception {
assertEquals("Failed to convert int* return to java.awt.Point", 1234, p.x);
assertEquals("Failed to convert int* return to java.awt.Point", 5678, p.y);
}
public static class DirectTypeMappedEnumerationTestLibrary {
public static enum Enumeration {
STATUS_0(0), STATUS_1(1), STATUS_ERROR(-1);
private final int code;
Enumeration(int code) { this.code = code; }
public int getCode() { return code; }
public static Enumeration fromCode(int code) {
switch(code) {
case 0: return STATUS_0;
case 1: return STATUS_1;
default: return STATUS_ERROR;
}
}
}
public native Enumeration returnInt32Argument(Enumeration e);
static {
DefaultTypeMapper mapper = new DefaultTypeMapper();
mapper.addTypeConverter(Enumeration.class, new TypeConverter() {
public Object toNative(Object arg, ToNativeContext ctx) {
return new Integer(((Enumeration)arg).getCode());
}
public Object fromNative(Object value, FromNativeContext context) {
return Enumeration.fromCode(((Integer)value).intValue());
}
public Class nativeType() {
return Integer.class;
}
});
Map options = new HashMap();
options.put(Library.OPTION_TYPE_MAPPER, mapper);

Native.register(NativeLibrary.getInstance("testlib", options));
}
}
public void testEnumerationConversion() {
DirectTypeMappedEnumerationTestLibrary lib = new DirectTypeMappedEnumerationTestLibrary();
DirectTypeMappedEnumerationTestLibrary.Enumeration e = lib.returnInt32Argument(DirectTypeMappedEnumerationTestLibrary.Enumeration.STATUS_1);
assertEquals("Failed to convert enumeration", DirectTypeMappedEnumerationTestLibrary.Enumeration.STATUS_1, e);
}

public static void main(String[] args) {
junit.textui.TestRunner.run(DirectTypeMapperTest.class);
Expand Down
39 changes: 38 additions & 1 deletion test/com/sun/jna/TypeMapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,44 @@ public Class nativeType() {
s.read();
assertFalse("Wrong value read", s.data);
}


public static enum Enumeration {
STATUS_0(0), STATUS_1(1), STATUS_ERROR(-1);
private final int code;
Enumeration(int code) { this.code = code; }
public int getCode() { return code; }
public static Enumeration fromCode(int code) {
switch(code) {
case 0: return STATUS_0;
case 1: return STATUS_1;
default: return STATUS_ERROR;
}
}
}
public static interface EnumerationTestLibrary extends Library {
Enumeration returnInt32Argument(Enumeration arg);
}
public void testEnumConversion() throws Exception {
DefaultTypeMapper mapper = new DefaultTypeMapper();
TypeConverter converter = new TypeConverter() {
public Object toNative(Object value, ToNativeContext ctx) {
return new Integer(((Enumeration)value).getCode());
}
public Object fromNative(Object value, FromNativeContext context) {
return Enumeration.fromCode(((Integer)value).intValue());
}
public Class nativeType() {
return Integer.class;
}
};
mapper.addTypeConverter(Enumeration.class, converter);
Map options = new HashMap();
options.put(Library.OPTION_TYPE_MAPPER, mapper);
EnumerationTestLibrary lib = (EnumerationTestLibrary)
Native.loadLibrary("testlib", EnumerationTestLibrary.class, options);
assertEquals("Enumeration improperly converted", Enumeration.STATUS_1, lib.returnInt32Argument(Enumeration.STATUS_1));
}

public static void main(String[] args) {
junit.textui.TestRunner.run(TypeMapperTest.class);
}
Expand Down