On Thu, Jan 7, 2010 at 4:32 PM, Alessio Stalla alessiostalla@gmail.com wrote:
On Thu, Jan 7, 2010 at 6:39 AM, Alan Ruttenberg alanruttenberg@gmail.com wrote:
I'm trying to use the new jcall code in place of jss calls. However I am getting an error I don't understand.
In the following:
(defun directory-in-jar (pathname) (let* ((jarfile (subseq (namestring (pathname-device pathname)) 9)) (rest-pathname (namestring (make-pathname :directory (pathname-directory pathname) :name (pathname-name pathname) :type (pathname-type pathname))))) (if (or (position #* (namestring rest-pathname)) (wild-pathname-p rest-pathname)) (let ((jar (jnew "java.util.zip.ZipFile" jarfile))) (let ((els (jcall "entries" jar))) (loop while (#"hasMoreElements" els) for name = (jcall "getName" (#"nextElement" els)) when (pathname-match-p (concatenate 'string "/" name) rest-pathname) collect (make-pathname :device (pathname-device pathname) :name (pathname-name name) :type (pathname-type name) :directory `(:absolute ,@(cdr (pathname-directory name))))))) (let ((truename (probe-file-in-jar pathname))) (if truename (list truename) nil)))))
If I replace the call (#"hasMoreElements" els) with (jcall "hasMoreElements" els) I get the error:
java.lang.IllegalAccessException: Class org.armedbear.lisp.Java can not access a member of class java.util.zip.ZipFile$3 with modifiers "public" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) at java.lang.reflect.Method.invoke(Method.java:583) at org.armedbear.lisp.Java.jcall(Java.java:660) ...
If I replace the call (#"nextElement" els) with (jcall "nextElement" els) I get the error
java.lang.IllegalAccessException: Class org.armedbear.lisp.Java can not access a member of class java.util.zip.ZipFile$3 with modifiers "public volatile" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) at java.lang.reflect.Method.invoke(Method.java:583) at org.armedbear.lisp.Java.jcall(Java.java:660)
This from http://svn.mumble.net:8080/svn/lsw/trunk/patches/jarfile.lisp where I'm starting to add more path functions that work with the jar:file: pathname scheme. Feel free to steal and incorporate into abcl.
This code fails too with the same error:
(let ((jar (jnew "java.util.zip.ZipFile" "/home/alessio/abcl/pippo.jar"))) (let ((els (jcall "entries" jar))) (let ((method (jmethod (jclass-of els) "hasMoreElements"))) (jcall method els))))
as does its translation to Java:
ZipFile jar = new ZipFile("/home/alessio/abcl/pippo.jar"); Object els = jar.entries(); Method method = els.getClass().getMethod("hasMoreElements"); method.invoke(els);
so it's some quirk of Java reflection that I'm not aware of. In fact, calling method.setAccessible(true); before invoking it removes the error. Maybe the inner classes ZipFile$x are not public, and that is sufficient to cause an IllegalAccessException? In any case, ABCL's jcall currently makes no attempts at ensuring the method is accessible before calling it.
hmm, it's weird. In the Java code I posted, using Method method = java.util.Enumeration.class.getMethod("hasMoreElements"); works, even without setAccessible(true)! Although with dynamic dispatch the method that gets called is really the same...
And it's even weirder: iterating through els.getClass().getMethods() gives
... public java.util.zip.ZipEntry java.util.zip.ZipFile$2.nextElement() throws java.util.NoSuchElementException declared in class java.util.zip.ZipFile$2 public java.lang.Object java.util.zip.ZipFile$2.nextElement() declared in class java.util.zip.ZipFile$2 ...
clearly one of the two is inherited from Enumeration, but it gets reported as being defined in the inner class, resulting in an apparently duplicated method!
There are several bugs filed for the JVM about this or similar issues, dating as far back as Java 1.2 (!!!); examples are http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4819108, http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957. To my understanding, implementing the proper checks efficiently in order to solve the problem would involve changing the class file format, so this bug has been forever postponed.
At this point, we can either
1. ignore the problem, 2. always call setAccessible(true) before calling a method, 3. try to find the overridden method as up as possible in the class hierarchy before calling it, hoping to get at some point to a public class/interface
2. and 3. would make all method calls through jcall less efficient; however, we could enable one of those techniques only when some additional parameter is passed to jcall (e.g. the try-harder that was proposed some time ago for jfield).
Bye, Ale