I've got a subtle problem: if I create a byte array, store it in a java.util.ArrayList, and then retrieve the stored byte array, what I get back is not quite the same type as the original byte array. I think that everything is the same, except that the JavaObjects have different values for the intendedClass field.
Here is an example:
(setq array1 (jnew-array "byte" 1))
(setq array2 (let ((array-list (jnew (jconstructor "java.util.ArrayList")))) ;; put the byte array into the ArrayList (jcall (jmethod "java.util.AbstractList" "add" "java.lang.Object") array-list array1) ;; and pull it back out (jcall (jmethod "java.util.AbstractList" "get" "int") array-list 0)))
(describe array1) ;#<jarray [B@c4ff22 {4A44DD}> is an object of type JAVA-OBJECT. ;The wrapped Java object is an array of bytes with 1 element.; No value
(describe array2) ;#<jarray [B@c4ff22 {5E6B62}> is an object of type JAVA-OBJECT. ;The wrapped Java object is an array of bytes with 1 element.; No value
At this point array1 and array2 look pretty compatible, but ...
(sys::%make-byte-array-input-stream array1) ; works fine, gives a #S(SYSTEM::SYSTEM-STREAM)
(sys::%make-byte-array-input-stream array2) ; gives a TYPE-ERROR, with message "java.lang.Object is not assignable to [B"
The TYPE-ERROR comes from JavaObject.javaInstance(Class<?> c) (line 248 of JavaObject.java). It seems that array1 has intendedClass="[B", and array2 has intendedClass="java.lang.Object", so I'm pretty sure that's my problem.
Is there a way to specify the intendedClass of array2?
Thanks,
-david k.
I was able to fix this (bug?)
Basically ABCL is missing a
if (cc.isInstance(obj)) return obj;
in the function
public <T> Object javaInstance(Class<T> c)
of JavaObject.java
In ABCL's src i'd go from:
@Override public <T> Object javaInstance(Class<T> c) { final Class cc; if (obj == null) { if (c.isPrimitive()) { throw new NullPointerException("Cannot assign null to " + c); } return obj; } else { cc = JavaFunctions.maybeBoxClass(c); if (cc.isAssignableFrom(intendedClass)) { return obj; } else { return error(new TypeError(intendedClass.getName() + " is not assignable to " + c.getName())); } } }
to
@Override public <T> Object javaInstance(Class<T> c) { final Class cc; if (obj == null) { if (c.isPrimitive()) { throw new NullPointerException("Cannot assign null to " + c); } return obj; } else { cc = JavaFunctions.maybeBoxClass(c); if (cc.isInstance(obj)) return obj; if (cc.isAssignableFrom(intendedClass)) { return obj; } else { return error(new TypeError(intendedClass.getName() + " is not assignable to " + c.getName())); } } }
A commiter should be able to help
On Mon, Mar 22, 2010 at 9:55 PM, David Kirkman dkirkman@ucsd.edu wrote:
I've got a subtle problem: if I create a byte array, store it in a java.util.ArrayList, and then retrieve the stored byte array, what I get back is not quite the same type as the original byte array. I think that everything is the same, except that the JavaObjects have different values for the intendedClass field.
Here is an example:
(setq array1 (jnew-array "byte" 1))
(setq array2 (let ((array-list (jnew (jconstructor "java.util.ArrayList")))) ;; put the byte array into the ArrayList (jcall (jmethod "java.util.AbstractList" "add" "java.lang.Object") array-list array1) ;; and pull it back out (jcall (jmethod "java.util.AbstractList" "get" "int") array-list 0)))
(describe array1) ;#<jarray [B@c4ff22 {4A44DD}> is an object of type JAVA-OBJECT. ;The wrapped Java object is an array of bytes with 1 element.; No value
(describe array2) ;#<jarray [B@c4ff22 {5E6B62}> is an object of type JAVA-OBJECT. ;The wrapped Java object is an array of bytes with 1 element.; No value
At this point array1 and array2 look pretty compatible, but ...
(sys::%make-byte-array-input-stream array1) ; works fine, gives a #S(SYSTEM::SYSTEM-STREAM)
(sys::%make-byte-array-input-stream array2) ; gives a TYPE-ERROR, with message "java.lang.Object is not assignable to [B"
The TYPE-ERROR comes from JavaObject.javaInstance(Class<?> c) (line 248 of JavaObject.java). It seems that array1 has intendedClass="[B", and array2 has intendedClass="java.lang.Object", so I'm pretty sure that's my problem.
Is there a way to specify the intendedClass of array2?
Thanks,
-david k.
armedbear-devel mailing list armedbear-devel@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/armedbear-devel
On 3/23/10 7:21 AM, dmiles@users.sourceforge.net wrote: […]
A commiter should be able to help
Patched as suggested in [r12570][1], although I am not entirely happy with this change yet (see below).
[1]: http://trac.common-lisp.net/armedbear/changeset/12570
On Mon, Mar 22, 2010 at 9:55 PM, David Kirkmandkirkman@ucsd.edu wrote:
I've got a subtle problem: if I create a byte array, store it in a java.util.ArrayList, and then retrieve the stored byte array, what I get back is not quite the same type as the original byte array. I think that everything is the same, except that the JavaObjects have different values for the intendedClass field.
[…]
Is there a way to specify the intendedClass of array2?
A bit baroque, but one uses JCOERCE. So, you could get your code to work via
(sys::%make-sys-output-stream (jcoerce array2 (jclass-of array1)))
but I think that this is a bit too much of a burden on the end user, so I patched the code as Douglas suggested.
I've also updated the INSPECT protocol for JAVA-OBJECT so that users (and user code) could have a chance at observing that the two version of the array had differences in any tool that uses SYS::GET-INSPECTED-PARTS (like the SLIME inspector).
As far as I understand this problem, the insertion/removal of the byte[] from the Java collection has erased the type information down to java.lang.Object. This type is "good enough" for the current constructors of JAVA-OBJECT (after all java.lang.Object is a valid type), but causes problems when being asked to return a Java reference in compiled Java code (like that in the SYS::%MAKE-BYTE-ARRAY-OUTPUT-STRING.)
My hunch is that need to tighten the JAVA-OBJECT constructors to check if they are being asked to wrap an array of primitive type, adjusting the value of intendedClass if this is the case. I'm reluctant to make such a change as a) I don't fully understand the ramifications of the difference between Class.isInstance() and Class.isAssignableFrom(), b) don't know if this only fails for arrays of primitive type, and c) don't have enough test coverage to judge such things quickly.
Comments and advice solicited.
On Tue, Mar 23, 2010 at 6:55 AM, Mark Evenson evenson@panix.com wrote:
On 3/23/10 7:21 AM, dmiles@users.sourceforge.net wrote: […]
A commiter should be able to help
Patched as suggested in [r12570][1], although I am not entirely happy with this change yet (see below).
Thanks, that fixes my trouble.
As far as I understand this problem, the insertion/removal of the byte[] from the Java collection has erased the type information down to java.lang.Object. This type is "good enough" for the current constructors of JAVA-OBJECT (after all java.lang.Object is a valid type), but causes problems when being asked to return a Java reference in compiled Java code (like that in the SYS::%MAKE-BYTE-ARRAY-OUTPUT-STRING.)
My hunch is that need to tighten the JAVA-OBJECT constructors to check if they are being asked to wrap an array of primitive type, adjusting the value of intendedClass if this is the case. I'm reluctant to make such a change as a) I don't fully understand the ramifications of the difference between Class.isInstance() and Class.isAssignableFrom(), b) don't know if this only fails for arrays of primitive type, and c) don't have enough test coverage to judge such things quickly.
Comments and advice solicited.
intendedType ends up as java.lang.Object for any java object I move into and out of an ArrayList -- not just arrays. As far as I can tell, lisp objects move back and forth with no trouble, (inspect ..) does not show any difference for a hash table I move back and forth, but (inspect ..) does show that a java.io.File moved back and forth ends up with a changed intendedType.
I'm doing a lot of this sort of thing, and the example with sys::%make-byte-array-output-string is the only thing that gave me trouble. Even when a java.io.File ends up with Object as it's intendedType, it still works fine with (jcall ...). I assume that there would only be trouble when JavaObject.javaInstance() is called, which is apparently not that often!
-david k.
On 3/23/10 5:58 PM, David Kirkman wrote: […]
Comments and advice solicited.
intendedType ends up as java.lang.Object for any java object I move into and out of an ArrayList -- not just arrays. As far as I can tell, lisp objects move back and forth with no trouble, (inspect ..) does not show any difference for a hash table I move back and forth, but (inspect ..) does show that a java.io.File moved back and forth ends up with a changed intendedType.
I'm doing a lot of this sort of thing, and the example with sys::%make-byte-array-output-string is the only thing that gave me trouble. Even when a java.io.File ends up with Object as it's intendedType, it still works fine with (jcall ...). I assume that there would only be trouble when JavaObject.javaInstance() is called, which is apparently not that often!
This partially answers my concerns about testing, as your use certainly sounds like it is validating the change and the current structure. The other part of my concerns would be addressed by more extensive tests for the Java FFI in our unit tests, which I will place on my TODO list.
On Tue, Mar 23, 2010 at 2:55 PM, Mark Evenson evenson@panix.com wrote:
On 3/23/10 7:21 AM, dmiles@users.sourceforge.net wrote: […]
A commiter should be able to help
Patched as suggested in [r12570][1], although I am not entirely happy with this change yet (see below).
On Mon, Mar 22, 2010 at 9:55 PM, David Kirkmandkirkman@ucsd.edu wrote:
I've got a subtle problem: if I create a byte array, store it in a java.util.ArrayList, and then retrieve the stored byte array, what I get back is not quite the same type as the original byte array. I think that everything is the same, except that the JavaObjects have different values for the intendedClass field.
[…]
Is there a way to specify the intendedClass of array2?
A bit baroque, but one uses JCOERCE. So, you could get your code to work via
(sys::%make-sys-output-stream (jcoerce array2 (jclass-of array1)))
but I think that this is a bit too much of a burden on the end user, so I patched the code as Douglas suggested.
I've also updated the INSPECT protocol for JAVA-OBJECT so that users (and user code) could have a chance at observing that the two version of the array had differences in any tool that uses SYS::GET-INSPECTED-PARTS (like the SLIME inspector).
As far as I understand this problem, the insertion/removal of the byte[] from the Java collection has erased the type information down to java.lang.Object. This type is "good enough" for the current constructors of JAVA-OBJECT (after all java.lang.Object is a valid type), but causes problems when being asked to return a Java reference in compiled Java code (like that in the SYS::%MAKE-BYTE-ARRAY-OUTPUT-STRING.)
My hunch is that need to tighten the JAVA-OBJECT constructors to check if they are being asked to wrap an array of primitive type, adjusting the value of intendedClass if this is the case. I'm reluctant to make such a change as a) I don't fully understand the ramifications of the difference between Class.isInstance() and Class.isAssignableFrom(), b) don't know if this only fails for arrays of primitive type, and c) don't have enough test coverage to judge such things quickly.
Comments and advice solicited.
I believe the isAssignableFrom check was just a plain mistake. The "intended type" can be as generic as possible (i.e. it can be java.lang.Object for any JavaObject) so it does not make much sense to check against it. Imho it's correct to only do the isInstance() call. Arrays of primitive types and arrays of corresponding wrapper types are not interchangeable in Java so there should be no concern about them in this context.
Note also that the intended type iirc is only used by jcall in its abbreviated form - (jcall "methodName" instance args) - to access methods using a supertype of the actual class of the object when possible. This means that the intended type can be set quite loosely; it's basically just a hint for jcall.
hth, Alessio
On 3/23/10 7:16 PM, Alessio Stalla wrote: […]
Comments and advice solicited.
I believe the isAssignableFrom check was just a plain mistake. The "intended type" can be as generic as possible (i.e. it can be java.lang.Object for any JavaObject) so it does not make much sense to check against it. Imho it's correct to only do the isInstance() call. Arrays of primitive types and arrays of corresponding wrapper types are not interchangeable in Java so there should be no concern about them in this context.
Note also that the intended type iirc is only used by jcall in its abbreviated form - (jcall "methodName" instance args) - to access methods using a supertype of the actual class of the object when possible. This means that the intended type can be set quite loosely; it's basically just a hint for jcall.
Thanks for the analysis, as it makes me quite a bit more comfortable with the change: I retract the "I'm unhappy with this change" comment, and consider this problem fixed.
armedbear-devel@common-lisp.net