diff --git a/src/main/java/org/bridj/StructFieldDeclaration.java b/src/main/java/org/bridj/StructFieldDeclaration.java index 2be78874..1aa5d732 100644 --- a/src/main/java/org/bridj/StructFieldDeclaration.java +++ b/src/main/java/org/bridj/StructFieldDeclaration.java @@ -37,7 +37,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.bridj.ann.Alignment; import org.bridj.ann.Array; @@ -45,6 +49,19 @@ import org.bridj.ann.Field; import org.bridj.ann.Union; +import com.fasterxml.classmate.AnnotationConfiguration; +import com.fasterxml.classmate.AnnotationInclusion; +import com.fasterxml.classmate.Filter; +import com.fasterxml.classmate.MemberResolver; +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.classmate.ResolvedTypeWithMembers; +import com.fasterxml.classmate.TypeResolver; +import com.fasterxml.classmate.members.RawField; +import com.fasterxml.classmate.members.RawMethod; +import com.fasterxml.classmate.members.ResolvedField; +import com.fasterxml.classmate.members.ResolvedMember; +import com.fasterxml.classmate.members.ResolvedMethod; + class StructFieldDeclaration { final StructFieldDescription desc = new StructFieldDescription(); @@ -58,6 +75,7 @@ public String toString() { return desc.name + " (index = " + index + (unionWith < 0 ? "" : ", unionWith = " + unionWith) + ", desc = " + desc + ")"; } + @Deprecated protected static boolean acceptFieldGetter(Member member, boolean getter) { if ((member instanceof Method) && ((Method) member).getParameterTypes().length != (getter ? 0 : 1)) { return false; @@ -71,16 +89,30 @@ protected static boolean acceptFieldGetter(Member member, boolean getter) { return !Modifier.isStatic(modifiers); } + + protected static boolean acceptFieldGetter(ResolvedMember member, boolean getter) { + if ((member instanceof ResolvedMethod) && ((ResolvedMethod) member).getRawMember().getParameterTypes().length != (getter ? 0 : 1)) { + return false; + } + + if (member.get(Field.class) == null) { + return false; + } + + return !member.isStatic(); + } /** * Creates a list of structure fields */ + @Deprecated protected static List listFields(Class structClass) { List list = new ArrayList(); for (Method method : structClass.getMethods()) { if (acceptFieldGetter(method, true)) { StructFieldDeclaration io = fromGetter(method); try { + // this only works when the names are equal, does not support setXXX methods. Method setter = structClass.getMethod(method.getName(), io.valueClass); if (acceptFieldGetter(setter, false)) { io.setter = setter; @@ -110,7 +142,81 @@ protected static List listFields(Class structClass) { return list; } + + protected static List listFields2(Class structClass) { + List list = new ArrayList(); + ResolvedTypeWithMembers resolvedStruct = resolveType(structClass); + for (ResolvedMethod method : resolvedStruct.getMemberMethods()) { + if (acceptFieldGetter(method, true)) { + StructFieldDeclaration io = fromGetter(method); + try { + // this only works when the names are equal, does not support setXXX methods. + ResolvedMethod setter = getMethod( resolvedStruct.getMemberMethods(), method.getName(), io.valueClass); + if (acceptFieldGetter(setter, false)) { + io.setter = setter.getRawMember(); + } + } catch (Exception ex) { + //assert BridJ.info("No setter for getter " + method); + } + if (io != null) { + list.add(io); + } + } + } + int nFieldFields = 0; + for ( ResolvedField field : resolvedStruct.getMemberFields()) { + if (acceptFieldGetter(field, true)) { + StructFieldDeclaration io = StructFieldDeclaration.fromField(field); + if (io != null) { + list.add(io); + nFieldFields++; + } + } + } + if (nFieldFields > 0 && BridJ.warnStructFields) { + BridJ.warning("Struct " + structClass.getName() + " has " + nFieldFields + " struct fields implemented as Java fields, which won't give the best performance and might require counter-intuitive calls to BridJ.readFromNative / .writeToNative. Please consider using JNAerator to generate your struct instead, or use BRIDJ_WARN_STRUCT_FIELDS=0 or -Dbridj.warnStructFields=false to mute this warning."); + } + + return list; + } + + public static ResolvedMethod getMethod( ResolvedMethod[] methods, String name, Class... params ) { + METHODS: for( ResolvedMethod method : methods ) { + if( !name.equals(method.getName()) ) continue METHODS; + if( params.length != method.getArgumentCount()) continue METHODS; + for( int i = 0; i < params.length; i++ ) { + if( !method.getArgumentType(i).isInstanceOf(params[i])) continue METHODS; + } + return method; + } + return null; + } + + protected static String nameForMember( ResolvedMember member ) { + String name = member.getName(); + if (name.matches("get[A-Z].*")) { + return Character.toLowerCase(name.charAt(3)) + name.substring(4); + } else if ( name.matches("set[A-Z].*")) { + return Character.toLowerCase(name.charAt(3)) + name.substring(4); + } else { + return name; + } + } + + protected static ResolvedTypeWithMembers resolveType( Class structClass ) { + TypeResolver resolver = new TypeResolver(); + ResolvedType classType = resolver.resolve(structClass); + MemberResolver mr = new MemberResolver(resolver); + mr.setMethodFilter(method-> + method.getRawMember().getParameterTypes().length < 2 && + !method.isStatic()); + mr.setFieldFilter(field->!field.isStatic()); + AnnotationConfiguration annConfig = new AnnotationConfiguration.StdConfiguration(AnnotationInclusion.INCLUDE_BUT_DONT_INHERIT); + return mr.resolve(classType, annConfig, null); + } + + @Deprecated protected static StructFieldDeclaration fromField(java.lang.reflect.Field getter) { StructFieldDeclaration field = fromMember((Member) getter); field.desc.field = getter; @@ -119,6 +225,15 @@ protected static StructFieldDeclaration fromField(java.lang.reflect.Field getter return field; } + protected static StructFieldDeclaration fromField(ResolvedField getter) { + StructFieldDeclaration field = fromMember((ResolvedField) getter); + field.desc.field = getter.getRawMember(); + field.desc.valueType = getter.getType(); + field.valueClass = getter.getType().getErasedType(); + return field; + } + + @Deprecated protected static StructFieldDeclaration fromGetter(Method getter) { StructFieldDeclaration field = fromMember((Member) getter); field.desc.getter = getter; @@ -127,6 +242,15 @@ protected static StructFieldDeclaration fromGetter(Method getter) { return field; } + protected static StructFieldDeclaration fromGetter(ResolvedMethod getter) { + StructFieldDeclaration field = fromMember((ResolvedMember) getter); + field.desc.getter = getter.getRawMember(); + field.desc.valueType = getter.getReturnType(); + field.valueClass = getter.getReturnType().getErasedType(); + return field; + } + + @Deprecated private static StructFieldDeclaration fromMember(Member member) { StructFieldDeclaration field = new StructFieldDeclaration(); field.declaringClass = member.getDeclaringClass(); @@ -170,4 +294,47 @@ private static StructFieldDeclaration fromMember(Member member) { field.desc.isSizeT = isAnnotationPresent(org.bridj.ann.Ptr.class, getter); return field; } + + private static StructFieldDeclaration fromMember(ResolvedMember member) { + StructFieldDeclaration field = new StructFieldDeclaration(); + field.declaringClass = member.getRawMember().getDeclaringClass(); + + String name = member.getName(); + if (name.matches("get[A-Z].*")) { + name = Character.toLowerCase(name.charAt(3)) + name.substring(4); + } + + field.desc.name = name; + + Field fil = member.get(Field.class); + Bits bits = member.get(Bits.class); + Alignment alignment = member.get(Alignment.class); + Array arr = member.get(Array.class); + if (fil != null) { + field.index = fil.value(); + //field.byteOffset = fil.offset(); + field.unionWith = fil.unionWith(); + } + if (field.unionWith < 0 && field.declaringClass.getAnnotation(Union.class) != null) { + field.unionWith = 0; + } + + if (bits != null) { + field.desc.bitLength = bits.value(); + } + if (alignment != null) { + field.desc.alignment = alignment.value(); + } + if (arr != null) { + long length = 1; + for (long dim : arr.value()) { + length *= dim; + } + field.desc.arrayLength = length; + field.desc.isArray = true; + } + field.desc.isCLong = member.get(org.bridj.ann.CLong.class) != null; + field.desc.isSizeT = member.get(org.bridj.ann.Ptr.class) != null; + return field; + } } diff --git a/src/main/java/org/bridj/StructFieldDescription.java b/src/main/java/org/bridj/StructFieldDescription.java index 4f4c2759..151c2021 100644 --- a/src/main/java/org/bridj/StructFieldDescription.java +++ b/src/main/java/org/bridj/StructFieldDescription.java @@ -55,6 +55,8 @@ import org.bridj.util.DefaultParameterizedType; import org.bridj.util.Utils; +import com.fasterxml.classmate.ResolvedType; + /** * Internal metadata on a struct field */ @@ -90,7 +92,12 @@ static Type resolveType(Type tpe, Type structType) { } Type ret; - if (tpe instanceof ParameterizedType) { + if (tpe instanceof ResolvedType ) { + ResolvedType rt = (ResolvedType)tpe; + // TODO: what do we do here? + ret = tpe; + } + else if (tpe instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) tpe; Type[] actualTypeArguments = pt.getActualTypeArguments(); Type[] resolvedActualTypeArguments = new Type[actualTypeArguments.length]; @@ -178,8 +185,18 @@ static StructFieldDescription aggregateDeclarations(Type structType, List fixIntegralTypeIOToMatchLength(Pointer ptr, long byteLength @SuppressWarnings("deprecation") protected static void computeStructLayout(StructDescription desc, StructCustomizer customizer) { - List fieldDecls = StructFieldDeclaration.listFields(desc.structClass); + List fieldDecls = StructFieldDeclaration.listFields2(desc.structClass); orderFields(fieldDecls); customizer.beforeAggregation(desc, fieldDecls); diff --git a/src/main/java/org/bridj/util/Utils.java b/src/main/java/org/bridj/util/Utils.java index a9c3b3b3..6087bbd8 100644 --- a/src/main/java/org/bridj/util/Utils.java +++ b/src/main/java/org/bridj/util/Utils.java @@ -48,6 +48,8 @@ import java.nio.LongBuffer; import java.nio.ShortBuffer; +import com.fasterxml.classmate.ResolvedType; + /** * Miscellaneous utility methods. * @@ -162,6 +164,9 @@ public static Class getClass(Type type) { if (type instanceof Class) { return (Class) type; } + if (type instanceof ResolvedType ) { + return (Class)((ResolvedType) type).getErasedType(); + } if (type instanceof ParameterizedType) { return getClass(((ParameterizedType) type).getRawType()); } diff --git a/src/main/velocity/org/bridj/PointerIO.java b/src/main/velocity/org/bridj/PointerIO.java index 5afa0aa8..7333159b 100644 --- a/src/main/velocity/org/bridj/PointerIO.java +++ b/src/main/velocity/org/bridj/PointerIO.java @@ -30,6 +30,8 @@ */ package org.bridj; +import com.fasterxml.classmate.ResolvedType; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; @@ -94,6 +96,8 @@ public Type getTargetType() { static Class getClass(Type type) { if (type instanceof Class) return (Class)type; + if (type instanceof ResolvedType) + return ((ResolvedType)type).getErasedType(); if (type instanceof ParameterizedType) return getClass(((ParameterizedType)type).getRawType()); return null; @@ -149,12 +153,24 @@ public static

PointerIO

getInstance(Type type) { if (type == null) return null; - PointerIO io = ios.get(type); + PointerIO io = null; + + if( type instanceof ResolvedType ) { + io = ios.get(((ResolvedType) type).getErasedType()); + } + if( io == null ) { + io = ios.get(type); + } if (io == null) { final Class cl = Utils.getClass(type); if (cl != null) { - if (cl == Pointer.class) - io = getPointerInstance(((ParameterizedType)type).getActualTypeArguments()[0]); + if (cl == Pointer.class) { + if( type instanceof ResolvedType ) { + io = getPointerInstance(((ResolvedType)type).getTypeParameters().get(0)); + } else { + io = getPointerInstance(((ParameterizedType)type).getActualTypeArguments()[0]); + } + } else if (StructObject.class.isAssignableFrom(cl)) io = getInstance(StructIO.getInstance((Class)cl, type)); else if (Callback.class.isAssignableFrom(cl)) @@ -162,7 +178,11 @@ else if (Callback.class.isAssignableFrom(cl)) else if (NativeObject.class.isAssignableFrom(cl)) io = new CommonPointerIOs.NativeObjectPointerIO(type); else if (IntValuedEnum.class.isAssignableFrom(cl)) { - if (type instanceof ParameterizedType) { + if (type instanceof ResolvedType) { + ResolvedType enumType = ((ResolvedType)type).getTypeParameters().get(0); + io = new CommonPointerIOs.IntValuedEnumPointerIO(enumType.getErasedType()); + } + else if (type instanceof ParameterizedType) { Type enumType = ((ParameterizedType)type).getActualTypeArguments()[0]; if (enumType instanceof Class) io = new CommonPointerIOs.IntValuedEnumPointerIO((Class)enumType); diff --git a/src/test/java/org/bridj/StructFieldDeclarationTest.java b/src/test/java/org/bridj/StructFieldDeclarationTest.java new file mode 100644 index 00000000..8e2128b7 --- /dev/null +++ b/src/test/java/org/bridj/StructFieldDeclarationTest.java @@ -0,0 +1,68 @@ +package org.bridj; + +import java.util.List; + +import org.bridj.ann.Field; +import org.junit.Test; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.CoreMatchers.*; + +public class StructFieldDeclarationTest { + + public static class BasicFieldStruct extends StructObject { + @Field(0) + public int intField; + } + + public static class StaticNestedStruct extends StructObject { + @Field(0) + public BasicFieldStruct structField; + } + + public static class GenericNestedStruct extends StructObject { + @Field(0) + public S structField; + } + + public static class NestedStructOfBasicFieldStruct + extends GenericNestedStruct {} + + @Test + public void testDifferences() { + List legacy = StructFieldDeclaration.listFields(BasicFieldStruct.class); + List resolved = StructFieldDeclaration.listFields2(BasicFieldStruct.class); + + assertThat(resolved.size(), equalTo(legacy.size())); + } + + @Test + public void testStaticNestedStruct() { + List legacy = StructFieldDeclaration.listFields(StaticNestedStruct.class); + List resolved = StructFieldDeclaration.listFields2(StaticNestedStruct.class); + + assertThat(resolved.size(), equalTo(legacy.size())); + + for( int i = 0; i < legacy.size(); i++ ) { + assertThat(resolved.get(i).declaringClass, equalTo(legacy.get(i).declaringClass)); + assertThat(resolved.get(i).valueClass, equalTo(legacy.get(i).valueClass)); + } + } + + @Test + public void testGenericNestedStruct() { + List legacy = StructFieldDeclaration.listFields(NestedStructOfBasicFieldStruct.class); + List resolved = StructFieldDeclaration.listFields2(NestedStructOfBasicFieldStruct.class); + + assertThat(resolved.size(), equalTo(legacy.size())); + + for( int i = 0; i < legacy.size(); i++ ) { + assertThat("declaringClass", resolved.get(i).declaringClass, equalTo(legacy.get(i).declaringClass)); + assertThat("legacyValueClass", legacy.get(i).valueClass, equalTo(StructObject.class)); + assertThat("resolvedValueClass", resolved.get(i).valueClass, equalTo(BasicFieldStruct.class)); + assertThat("index", resolved.get(i).index, equalTo(legacy.get(i).index)); + assertThat("setter", resolved.get(i).setter, equalTo(legacy.get(i).setter)); + assertThat("index", resolved.get(i).unionWith, equalTo(legacy.get(i).unionWith)); + + } + } +} diff --git a/src/test/java/org/bridj/StructGenericsTest.java b/src/test/java/org/bridj/StructGenericsTest.java new file mode 100644 index 00000000..1539626e --- /dev/null +++ b/src/test/java/org/bridj/StructGenericsTest.java @@ -0,0 +1,51 @@ +package org.bridj; + +import static org.bridj.Pointer.getPointer; + +import org.bridj.ann.Field; +import org.junit.Test; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; + +public class StructGenericsTest { + public static abstract class AbstractStructField> extends StructObject { + @Field(0) + public S getStruct() { + return io.getNativeObjectField(this, 0); + } + + public T setStruct( S struct ) { + io.setNativeObjectField(this, 0, struct); + return (T)this; + } + } + + public static class StructOfInteger extends StructObject { + @Field(0) + public int getField() { + return io.getIntField(this, 0); + } + + public StructOfInteger setField( int field ) { + io.setIntField(this, 0, field); + return this; + } + } + + public static class NestedStructOfInteger + extends AbstractStructField {} + + @Test + public void shouldSupportNestedStructOfPrimative() { + NestedStructOfInteger s = new NestedStructOfInteger(); + s.setStruct(new StructOfInteger().setField(2)); + BridJ.writeToNative(s); + s = getPointer(s).get(); + MatcherAssert.assertThat(s.getStruct().getField(), CoreMatchers.equalTo(2)); + } + + @Test + public void shouldSupportNestedStructs() { + + } +}