source: tags/1.3.3/contrib/jss/invoke.lisp

Last change on this file was 14726, checked in by Mark Evenson, 10 years ago

jss: non-functional fixup to previous commit.

Renormalize whitespace.

Previous commit addressed formating JSS:NO-SUCH-JAVA-FIELD invocation.

File size: 30.0 KB
Line 
1;; Copyright (C) 2005 Alan Ruttenberg
2;; Copyright (C) 2011-2 Mark Evenson
3;;
4;; Since most of this code is derivative of the Jscheme System, it is
5;; licensed under the same terms, namely:
6
7;; This software is provided 'as-is', without any express or
8;; implied warranty.
9
10;; In no event will the author be held liable for any damages
11;; arising from the use of this software.
12
13;; Permission is granted to anyone to use this software for any
14;; purpose, including commercial applications, and to alter it
15;; and redistribute it freely, subject to the following
16;; restrictions:
17
18;; 1. The origin of this software must not be misrepresented; you
19;;    must not claim that you wrote the original software. If you
20;;    use this software in a product, an acknowledgment in the
21;;    product documentation would be appreciated but is not
22;;    required.
23
24;; 2. Altered source versions must be plainly marked as such, and
25;;    must not be misrepresented as being the original software.
26
27;; 3. This notice may not be removed or altered from any source
28;;    distribution.
29
30
31;; The dynamic dispatch of the java.lang.reflect package is used to
32;; make it real easy, if perhaps less efficient, to write Java code
33;; since you don't need to be bothered with imports, or with figuring
34;; out which method to call.  The only time that you need to know a
35;; class name is when you want to call a static method, or a
36;; constructor, and in those cases, you only need to know enough of
37;; the class name that is unique wrt to the classes on your classpath.
38;;
39;; Java methods look like this: #"toString". Java classes are
40;; represented as symbols, which are resolved to the appropriate java
41;; class name. When ambiguous, you need to be more specific. A simple example:
42
43;; (let ((sw (new 'StringWriter)))
44;;   (#"write" sw "Hello ")
45;;   (#"write" sw "World")
46;;   (print (#"toString" sw)))
47
48;; What's happened here? First, all the classes in all the jars in the
49;; classpath have been collected.  For each class a.b.C.d, we have
50;; recorded that b.c.d, b.C.d, C.d, c.d, and d potentially refer to
51;; this class. In your call to new, as long as the symbol can refer to
52;; only one class, we use that class. In this case, it is
53;; java.io.StringWriter. You could also have written (new
54;; 'io.stringwriter), (new '|io.StringWriter|), (new
55;; 'java.io.StringWriter)...
56
57;; the call (#"write" sw "Hello "), uses the code in invoke.java to
58;; call the method named "write" with the arguments sw and "Hello ".
59;; JSS figures out the right java method to call, and calls it.
60
61;; If you want to do a raw java call, use #0"toString". Raw calls
62;; return their results as Java objects, avoiding doing the usual Java
63;; object to Lisp object conversions that ABCL does.
64
65;; (with-constant-signature ((name jname raw?)*) &body body)
66;; binds a macro which expands to a jcall, promising that the same method
67;; will be called every time. Use this if you are making a lot of calls and
68;; want to avoid the overhead of a the dynamic dispatch.
69;; e.g. (with-constant-signature ((tostring "toString"))
70;;        (time (dotimes (i 10000) (tostring "foo"))))
71;; runs about 3x faster than (time (dotimes (i 10000) (#"toString" "foo")))
72;;
73;; (with-constant-signature ((tostring "toString" t)) ...) will cause the
74;; toString to be a raw java call. see get-all-jar-classnames below for an example.
75;;
76;; Implementation is that the first time the function is called, the
77;; method is looked up based on the arguments passed, and thereafter
78;; that method is called directly.  Doesn't work for static methods at
79;; the moment (lazy)
80;;
81;; (japropos string) finds all class names matching string
82;; (jcmn class-name) lists the names of all methods for the class
83;;
84;; TODO
85;;   - Make with-constant-signature work for static methods too.
86;;   - #2"toString" to work like function scoped (with-constant-signature ((tostring "toString")) ...)
87;;   - #3"toString" to work like runtime scoped (with-constant-signature ((tostring "toString")) ...)
88;;      (both probably need compiler support to work)
89;;   - Maybe get rid of second " in reader macro. #"toString looks nicer, but might
90;;     confuse lisp mode.
91;;   - write jmap, analogous to map, but can take java collections, java arrays etc.
92;;   - write loop clauses for java collections.
93;;   - Register classes in .class files below classpath directories (when :wild-inferiors works)
94;;   - Make documentation like Edi Weitz
95;;
96;; Thanks: Peter Graves, Jscheme developers, Mike Travers for skij, 
97;; Andras Simon for jfli-abcl which bootstrapped me and taught me how to do
98;; get-all-jar-classnames
99;;
100
101;; changelog
102
103;; Sat January 28, 2006, alanr:
104
105;; Change imports strategy. Only index by last part of class name,
106;; case insensitive. Make the lookup-class-name logic be a bit more
107;; complicated. This substantially reduces the time it takes to do the
108;; auto imports and since class name lookup is relatively infrequent,
109;; and in any case cached, this doesn't effect run time speed.  (did
110;; try caching, but didn't pay - more time was spent reading and
111;; populating large hash table)
112;;
113;; Split class path by ";" in addition to ":" for windows.
114;;
115;; Tested on windows, linux.
116
117;; 2011-05-21 Mark Evenson
118;;   "ported" to native ABCL without needing the jscheme.jar or bsh-2.0b4.jar
119
120(in-package :jss)
121
122(eval-when (:compile-toplevel :load-toplevel :execute)
123  (defvar *do-auto-imports* t 
124    "Whether to automatically introspect all Java classes on the classpath when JSS is loaded."))
125
126(defvar *imports-resolved-classes* (make-hash-table :test 'equal))
127
128(defun find-java-class (name)
129  "Returns the java.lang.Class representation of NAME.
130
131NAME can either string or a symbol according to the usual JSS conventions."
132  (jclass (maybe-resolve-class-against-imports name)))
133
134(defmacro invoke-add-imports (&rest imports)
135  "Push these imports onto the search path. If multiple, earlier in list take precedence"
136  `(eval-when (:compile-toplevel :load-toplevel :execute)
137     (clrhash *imports-resolved-classes*)
138     (dolist (i (reverse ',imports))
139       (setq *imports-resolved-classes* (delete i *imports-resolved-classes* :test 'equal))
140       )))
141
142(defun clear-invoke-imports ()
143  (clrhash *imports-resolved-classes*))
144
145(defun maybe-resolve-class-against-imports (classname)
146  (or (gethash classname *imports-resolved-classes*)
147      (let ((found (lookup-class-name classname)))
148        (if found
149            (progn 
150              (setf (gethash classname *imports-resolved-classes*) found)
151              found)
152            (string classname)))))
153
154(defvar *class-name-to-full-case-insensitive* (make-hash-table :test 'equalp))
155
156;; This is the function that calls invoke to call your java
157;; method. The first argument is the method name or 'new. The second
158;; is the object you are calling it on, followed by the rest of the
159;; arguments. If the "object" is a symbol, then that symbol is assumed
160;; to be a java class, and a static method on the class is called,
161;; otherwise a regular method is called.
162
163(defun invoke (method object &rest args)
164  (invoke-restargs method object args))
165
166(defun invoke-restargs (method object args &optional (raw? nil))
167  (let* ((object-as-class-name 
168          (if (symbolp object) (maybe-resolve-class-against-imports object)))
169         (object-as-class 
170          (if object-as-class-name (find-java-class object-as-class-name))))
171    (if (eq method 'new)
172        (apply #'jnew (or object-as-class-name object) args)
173        (if raw?
174            (if (symbolp object)
175                (apply #'jstatic-raw method object-as-class  args)
176                (apply #'jcall-raw method object  args))
177            (if (symbolp object)
178                (apply #'jstatic method object-as-class args)
179                (apply #'jcall method object args))))))
180
181(defconstant +set-accessible+ 
182  (jmethod "java.lang.reflect.AccessibleObject" "setAccessible" "boolean"))
183
184(defun invoke-find-method (method object args)
185  (let ((result 
186         (if (symbolp object)
187                ;;; static method
188             (apply #'jmethod (lookup-class-name object) 
189                    method (mapcar #'jobject-class args))
190                  ;;; instance method
191             (apply #'jresolve-method 
192                    method object args))))
193    (jcall +set-accessible+ result +true+)
194    result))
195
196;; This is the reader macro for java methods. it translates the method
197;; into a lambda form that calls invoke. Which is nice because you
198;; can, e.g. do this: (mapcar #"toString" list-of-java-objects). The reader
199;; macro takes one arg. If 0, then jstatic-raw is called, so that abcl doesn't
200;; automagically convert the returned java object into a lisp object. So
201;; #0"toString" returns a java.lang.String object, where as #"toString" returns
202;; a regular Lisp string as ABCL converts the Java string to a Lisp string.
203
204(eval-when (:compile-toplevel :load-toplevel :execute)
205  (defun read-invoke (stream char arg) 
206    (unread-char char stream)
207    (let ((name (read stream)))
208      (let ((object-var (gensym))
209            (args-var (gensym)))
210        `(lambda (,object-var &rest ,args-var) 
211           (invoke-restargs ,name  ,object-var ,args-var ,(eql arg 0))))))
212  (set-dispatch-macro-character #\# #\" 'read-invoke))
213
214(defmacro with-constant-signature (fname-jname-pairs &body body)
215  "Expand all references to FNAME-JNAME-PAIRS in BODY into static function calls promising that the same function bound in the FNAME-JNAME-PAIRS will be invoked with the same argument signature.
216
217FNAME-JNAME-PAIRS is a list of (symbol function &optional raw)
218elements where symbol will be the symbol bound to the method named by
219the string function.  If the optional parameter raw is non-nil, the
220result will be the raw JVM object, uncoerced by the usual conventions.
221
222Use this macro if you are making a lot of calls and
223want to avoid the overhead of the dynamic dispatch."
224
225  (if (null fname-jname-pairs)
226      `(progn ,@body)
227      (destructuring-bind ((fname jname &optional raw) &rest ignore) fname-jname-pairs
228        (declare (ignore ignore))
229        (let ((varname (gensym)))
230          `(let ((,varname nil))
231             (macrolet ((,fname (&rest args)
232                          `(if ,',varname
233                               (if ,',raw
234                                   (jcall-raw ,',varname ,@args)
235                                   (jcall ,',varname ,@args))
236                               (progn
237                                 (setq ,',varname (invoke-find-method ,',jname ,(car args) (list ,@(rest args))))
238                                 (if ,',raw
239                                     (jcall-raw ,',varname ,@args)
240                                     (jcall ,',varname ,@args))))))
241               (with-constant-signature ,(cdr fname-jname-pairs)
242                 ,@body)))))))
243
244(defun lookup-class-name (name)
245  (setq name (string name))
246  (let* (;; cant (last-name-pattern (#"compile" '|java.util.regex.Pattern| ".*?([^.]*)$"))
247         ;; reason: bootstrap - the class name would have to be looked up...
248         (last-name-pattern (load-time-value (jstatic (jmethod "java.util.regex.Pattern" "compile"
249                                                               (jclass "java.lang.String"))
250                                                      (jclass "java.util.regex.Pattern") 
251                                                      ".*?([^.]*)$")))
252         (last-name 
253          (let ((matcher (#0"matcher" last-name-pattern name)))
254            (#"matches" matcher)
255            (#"group" matcher 1))))
256    (let* ((bucket (gethash last-name *class-name-to-full-case-insensitive*))
257           (bucket-length (length bucket)))
258      (or (find name bucket :test 'equalp)
259          (flet ((matches-end (end full test)
260                   (= (+ (or (search end full :from-end t :test test) -10)
261                         (length end))
262                      (length full)))
263                 (ambiguous (choices)
264                   (error "Ambiguous class name: ~a can be ~{~a~^, ~}" name choices)))
265            (if (zerop bucket-length)
266                name
267                (let ((matches (loop for el in bucket when (matches-end name el 'char=) collect el)))
268                  (if (= (length matches) 1)
269                      (car matches)
270                      (if (= (length matches) 0)
271                          (let ((matches (loop for el in bucket when (matches-end name el 'char-equal) collect el)))
272                            (if (= (length matches) 1)
273                                (car matches)
274                                (if (= (length matches) 0)
275                                    name
276                                    (ambiguous matches))))
277                          (ambiguous matches))))))))))
278
279(defun get-all-jar-classnames (jar-file-name)
280  (let* ((jar (jnew (jconstructor "java.util.jar.JarFile" (jclass "java.lang.String")) (namestring (truename jar-file-name))))
281         (entries (#"entries" jar)))
282    (with-constant-signature ((matcher "matcher" t) (substring "substring")
283                              (jreplace "replace" t) (jlength "length")
284                              (matches "matches") (getname "getName" t)
285                              (next "nextElement" t) (hasmore "hasMoreElements")
286                              (group "group"))
287      (loop while (hasmore entries)
288         for name =  (getname (next entries))
289         with class-pattern = (#"compile" '|java.util.regex.Pattern| "[^$]*\\.class$")
290         with name-pattern = (#"compile" '|java.util.regex.Pattern| ".*?([^.]*)$")
291         when (matches (matcher class-pattern name))
292         collect
293           (let* ((fullname (substring (jreplace name #\/ #\.) 0 (- (jlength name) 6)))
294                  (matcher (matcher name-pattern fullname))
295                  (name (progn (matches matcher) (group matcher 1))))
296             (cons name fullname))
297           ))))
298
299(defun jar-import (file)
300  "Import all the Java classes contained in the pathname FILE into the JSS dynamic lookup cache."
301  (when (probe-file file)
302    (loop for (name . full-class-name) in (get-all-jar-classnames file)
303       do 
304         (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
305                  :test 'equal))))
306
307(defun new (class-name &rest args)
308  "Invoke the Java constructor for CLASS-NAME with ARGS.
309
310CLASS-NAME may either be a symbol or a string according to the usual JSS conventions."
311  (invoke-restargs 'new class-name args))
312
313(defvar *running-in-osgi* (ignore-errors (jclass "org.osgi.framework.BundleActivator")))
314
315(define-condition no-such-java-field (error)
316  ((field-name
317    :initarg :field-name
318    :reader field-name
319    )
320   (object
321    :initarg :object
322    :reader object
323    ))
324  (:report (lambda (c stream)
325             (format stream "Unable to find a FIELD named ~a for ~a"
326                     (field-name c) (object c))))
327  )
328
329(defun get-java-field (object field &optional (try-harder *running-in-osgi*))
330  "Get the value of the FIELD contained in OBJECT.
331If OBJECT is a symbol it names a dot qualified static FIELD."
332  (if try-harder
333      (let* ((class (if (symbolp object)
334                        (setq object (find-java-class object))
335                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)))
336                            object
337                            (jobject-class object))))
338             (jfield (if (java-object-p field)
339                         field
340                         (or (find-declared-field field class)
341                             (error 'no-such-java-field :field-name field :object object)))))
342        (#"setAccessible" jfield +true+)
343        (values (#"get" jfield object) jfield))
344      (if (symbolp object)
345          (let ((class (find-java-class object)))
346            (jfield class field))
347          (jfield field object))))
348
349(defun find-declared-field (field class)
350  "Return a FIELD object corresponding to the definition of FIELD
351\(a string\) visible at CLASS. *Not* restricted to public classes, and checks
352all superclasses of CLASS.
353   Returns NIL if no field object is found."
354  (loop while class
355     for field-obj = (get-declared-field class field)
356     if field-obj
357     do (return-from find-declared-field field-obj)
358     else
359     do (setf class (jclass-superclass class)))
360  nil)
361
362(defun get-declared-field (class fieldname)
363  (find fieldname (#"getDeclaredFields" class)
364        :key 'jfield-name :test 'equal))
365
366;; TODO use #"getSuperclass" and #"getInterfaces" to see whether there
367;; are fields in superclasses that we might set
368(defun set-java-field (object field value &optional (try-harder *running-in-osgi*))
369  "Set the FIELD of OBJECT to VALUE.
370If OBJECT is a symbol, it names a dot qualified Java class to look for
371a static FIELD.  If OBJECT is an instance of java:java-object, the
372associated is used to look up the static FIELD."
373  (if try-harder
374      (let* ((class (if (symbolp object)
375                        (setq object (find-java-class object))
376                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)) )
377                            object
378                            (jobject-class object))))
379             (jfield (if (java-object-p field)
380                         field
381                         (or (find-declared-field field class)
382                             (error 'no-such-java-field :field-name field :object object)))))
383        (#"setAccessible" jfield +true+)
384        (values (#"set" jfield object value) jfield))
385      (if (symbolp object)
386          (let ((class (find-java-class object)))
387            (setf (jfield (#"getName" class) field) value))
388          (if (typep object 'java-object)
389              (setf (jfield (jclass-of object) field) value)
390              (setf (jfield object field) value)))))
391
392(defun (setf get-java-field) (value object field &optional (try-harder *running-in-osgi*))
393  (set-java-field object field value try-harder))
394
395
396(defconstant +for-name+ 
397  (jmethod "java.lang.Class" "forName" "java.lang.String" "boolean" "java.lang.ClassLoader"))
398
399(defun find-java-class (name)
400  (or (jstatic +for-name+ "java.lang.Class" 
401               (maybe-resolve-class-against-imports name) +true+ java::*classloader*)
402      (ignore-errors (jclass (maybe-resolve-class-against-imports name)))))
403
404(defmethod print-object ((obj (jclass "java.lang.Class")) stream) 
405  (print-unreadable-object (obj stream :identity nil)
406    (format stream "java class ~a" (jclass-name obj))))
407
408(defmethod print-object ((obj (jclass "java.lang.reflect.Method")) stream) 
409  (print-unreadable-object (obj stream :identity nil)
410    (format stream "method ~a" (#"toString" obj))))
411
412(defun do-auto-imports ()
413  (labels ((expand-paths (cp)
414             (loop :for s :in cp
415                :appending (loop :for entry 
416                              :in (let ((p (pathname s)))
417                                    (if (wild-pathname-p p)
418                                        (directory p)
419                                        (list p)))
420                              :collecting entry)))
421           (import-classpath (cp)
422             (mapcar 
423              (lambda (p) 
424                (when *load-verbose*
425                  (format t ";; Importing ~A~%" p))
426                (cond 
427                  ((file-directory-p p) )
428                  ((equal (pathname-type p) "jar")
429                   (jar-import (merge-pathnames p
430                                                (format nil "~a/" (jstatic "getProperty" "java.lang.System" "user.dir")))))))
431              cp))
432           (split-classpath (cp)
433             (coerce 
434              (jcall "split" cp 
435                     (string (jfield (jclass "java.io.File") "pathSeparatorChar")))
436              'cons))
437           (do-imports (cp)
438             (import-classpath (expand-paths (split-classpath cp)))))
439    (do-imports (jcall "getClassPath" (jstatic "getRuntimeMXBean" '|java.lang.management.ManagementFactory|)))
440    (do-imports (jcall "getBootClassPath" (jstatic "getRuntimeMXBean" '|java.lang.management.ManagementFactory|)))))
441
442(eval-when (:load-toplevel :execute)
443  (when *do-auto-imports* 
444    (do-auto-imports)))
445
446(defun japropos (string)
447  "Output the names of all Java class names loaded in the current process which match STRING.."
448  (setq string (string string))
449  (let ((matches nil))
450    (maphash (lambda(key value) 
451               (declare (ignore key))
452               (loop for class in value
453                  when (search string class :test 'string-equal)
454                  do (pushnew (list class "Java Class") matches :test 'equal)))
455             *class-name-to-full-case-insensitive*)
456    (loop for (match type) in (sort matches 'string-lessp :key 'car)
457       do (format t "~a: ~a~%" match type))
458    ))
459
460(defun jclass-method-names (class &optional full)
461  (if (java-object-p class)
462      (if (equal (jclass-name (jobject-class class)) "java.lang.Class")
463          (setq class (jclass-name class))
464          (setq class (jclass-name (jobject-class class)))))
465  (union
466   (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getMethods" (find-java-class class))) :test 'equal)
467   (ignore-errors (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getConstructors" (find-java-class class))) :test 'equal))))
468
469(defun java-class-method-names (class &optional stream)
470  "Return a list of the public methods encapsulated by the JVM CLASS.
471
472If STREAM non-nil, output a verbose description to the named output stream.
473
474CLASS may either be a string naming a fully qualified JVM class in dot
475notation, or a symbol resolved against all class entries in the
476current classpath."
477  (if stream
478      (dolist (method (jclass-method-names class t))
479        (format stream "~a~%" method))
480      (jclass-method-names class)))
481
482(setf (symbol-function 'jcmn) 'java-class-method-names)
483
484(defun path-to-class (classname)
485  (let ((full (lookup-class-name classname)))
486    (#"toString" 
487     (#"getResource" 
488      (find-java-class full)
489      (concatenate 'string "/" (substitute #\/ #\. full) ".class")))))
490
491;; http://www.javaworld.com/javaworld/javaqa/2003-07/02-qa-0725-classsrc2.html
492
493(defun all-loaded-classes ()
494  (let ((classes-field 
495         (find "classes" (#"getDeclaredFields" (jclass "java.lang.ClassLoader"))
496               :key #"getName" :test 'equal)))
497    (#"setAccessible" classes-field +true+)
498    (loop for classloader in (mapcar #'first (dump-classpath))
499       append
500         (loop with classesv = (#"get" classes-field classloader)
501            for i below (#"size" classesv)
502            collect (#"getName" (#"elementAt" classesv i)))
503       append
504         (loop with classesv = (#"get" classes-field (#"getParent" classloader))
505            for i below (#"size" classesv)
506            collect (#"getName" (#"elementAt" classesv i))))))
507
508(defun get-dynamic-class-path ()
509  (rest 
510   (find-if (lambda (loader) 
511              (string= "org.armedbear.lisp.JavaClassLoader"
512                       (jclass-name (jobject-class loader))))
513            (dump-classpath)
514            :key #'car)))
515
516(defun java-gc ()
517  (#"gc" (#"getRuntime" 'java.lang.runtime))
518  (#"runFinalization" (#"getRuntime" 'java.lang.runtime))
519  (#"gc" (#"getRuntime" 'java.lang.runtime))
520  (java-room))
521
522(defun java-room ()
523  (let ((rt (#"getRuntime" 'java.lang.runtime)))
524    (values (- (#"totalMemory" rt) (#"freeMemory" rt))
525            (#"totalMemory" rt)
526            (#"freeMemory" rt)
527            (list :used :total :free))))
528
529(defun verbose-gc (&optional (new-value nil new-value-supplied))
530  (if new-value-supplied
531      (progn (#"setVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory) new-value) new-value)
532      (#"isVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory))))
533
534(defun all-jars-below (directory) 
535  (loop with q = (system:list-directory directory) 
536     while q for top = (pop q)
537     if (null (pathname-name top)) do (setq q (append q (all-jars-below top))) 
538     if (equal (pathname-type top) "jar") collect top))
539
540(defun all-classfiles-below (directory) 
541  (loop with q = (system:list-directory directory) 
542     while q for top = (pop q)
543     if (null (pathname-name top)) do (setq q (append q (all-classfiles-below top ))) 
544     if (equal (pathname-type top) "class")
545     collect top
546       ))
547
548(defun all-classes-below-directory (directory)
549  (loop for file in (all-classfiles-below directory) collect
550       (format nil "~{~a.~}~a"
551               (subseq (pathname-directory file) (length (pathname-directory directory)))
552               (pathname-name file))
553       ))
554
555(defun classfiles-import (directory)
556  "Load all Java classes recursively contained under DIRECTORY in the current process."
557  (setq directory (truename directory))
558  (loop for full-class-name in (all-classes-below-directory directory)
559     for name = (#"replaceAll" full-class-name "^.*\\." "")
560     do
561       (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
562                :test 'equal)))
563
564(defun set-to-list (set)
565  (declare (optimize (speed 3) (safety 0)))
566  (with-constant-signature ((iterator "iterator" t) (hasnext "hasNext") (next "next"))
567    (loop with iterator = (iterator set)
568       while (hasNext iterator)
569       for item = (next iterator)
570       collect item)))
571
572(defun jlist-to-list (list)
573  "Convert a LIST implementing java.util.List to a Lisp list."
574  (declare (optimize (speed 3) (safety 0)))
575  (loop :for i :from 0 :below (jcall "size" list)
576     :collecting (jcall "get" list i)))
577
578(defun jarray-to-list (jarray)
579  "Convert the Java array named by JARRARY into a Lisp list."
580  (declare (optimize (speed 3) (safety 0)))
581  (loop :for i :from 0 :below (jarray-length jarray)
582     :collecting (jarray-ref jarray i)))
583
584;;; Deprecated
585;;;
586;;; XXX unclear what sort of list this would actually work on, as it
587;;; certainly doesn't seem to be any of the Java collection types
588;;; (what implements getNext())?
589(defun list-to-list (list)
590  (declare (optimize (speed 3) (safety 0)))
591  (with-constant-signature ((isEmpty "isEmpty") (getfirst "getFirst")
592                            (getNext "getNext"))
593    (loop until (isEmpty list)
594       collect (getFirst list)
595       do (setq list (getNext list)))))
596
597;; Contribution of Luke Hope. (Thanks!)
598
599(defun iterable-to-list (iterable)
600  "Return the items contained the java.lang.Iterable ITERABLE as a list."
601  (declare (optimize (speed 3) (safety 0)))
602  (let ((it (#"iterator" iterable)))
603    (with-constant-signature ((has-next "hasNext")
604                              (next "next"))
605      (loop :while (has-next it)
606         :collect (next it)))))
607
608(defun vector-to-list (vector)
609  "Return the elements of java.lang.Vector VECTOR as a list."
610  (declare (optimize (speed 3) (safety 0)))
611  (with-constant-signature ((has-more "hasMoreElements")
612                            (next "nextElement"))
613    (let ((elements (#"elements" vector)))
614      (loop :while (has-more elements)
615         :collect (next elements)))))
616
617(defun hashmap-to-hashtable (hashmap &rest rest &key (keyfun #'identity) (valfun #'identity) (invert? nil)
618                                                  table 
619                             &allow-other-keys )
620  "Converts the a HASHMAP reference to a java.util.HashMap object to a Lisp hashtable.
621
622The REST paramter specifies arguments to the underlying MAKE-HASH-TABLE call.
623
624KEYFUN and VALFUN specifies functions to be run on the keys and values
625of the HASHMAP right before they are placed in the hashtable.
626
627If INVERT? is non-nil than reverse the keys and values in the resulting hashtable."
628  (let ((keyset (#"keySet" hashmap))
629        (table (or table (apply 'make-hash-table
630                                (loop for (key value) on rest by #'cddr
631                                   unless (member key '(:invert? :valfun :keyfun :table)) 
632                                   collect key and collect value)))))
633    (with-constant-signature ((iterator "iterator" t) (hasnext "hasNext") (next "next"))
634      (loop with iterator = (iterator keyset)
635         while (hasNext iterator)
636         for item = (next iterator)
637         do (if invert?
638                (setf (gethash (funcall valfun (#"get" hashmap item)) table) (funcall keyfun item))
639                (setf (gethash (funcall keyfun item) table) (funcall valfun (#"get" hashmap item)))))
640      table)))
641
642(defun jclass-all-interfaces (class)
643  "Return a list of interfaces the class implements"
644  (unless (java-object-p class)
645    (setq class (find-java-class class)))
646  (loop for aclass = class then (#"getSuperclass" aclass)
647     while aclass
648     append (coerce (#"getInterfaces" aclass) 'list)))
649
650(defun safely (f name)
651  (let ((fname (gensym)))
652    (compile fname
653             `(lambda(&rest args)
654                (with-simple-restart (top-level
655                                      "Return from lisp method implementation for ~a." ,name)
656                  (apply ,f args))))
657    (symbol-function fname)))
658
659(defun jdelegating-interface-implementation (interface dispatch-to &rest method-names-and-defs)
660  "Creates and returns an implementation of a Java interface with
661   methods calling Lisp closures as given in METHOD-NAMES-AND-DEFS.
662
663   INTERFACE is an interface
664
665   DISPATCH-TO is an existing Java object
666
667   METHOD-NAMES-AND-DEFS is an alternating list of method names
668   (strings) and method definitions (closures).
669
670   For missing methods, a dummy implementation is provided that
671   calls the method on DISPATCH-TO."
672  (let ((implemented-methods
673         (loop for m in method-names-and-defs
674            for i from 0
675            if (evenp i) 
676            do (assert (stringp m) (m) "Method names must be strings: ~s" m) and collect m
677            else
678            do (assert (or (symbolp m) (functionp m)) (m) "Methods must be function designators: ~s" m))))
679    (let ((safe-method-names-and-defs 
680           (loop for (name function) on method-names-and-defs by #'cddr
681              collect name collect (safely function name))))
682      (loop for method across
683           (jclass-methods interface :declared nil :public t)
684         for method-name = (jmethod-name method)
685         when (not (member method-name implemented-methods :test #'string=))
686         do
687           (let* ((def  `(lambda
688                             (&rest args)
689                           (invoke-restargs ,(jmethod-name method) ,dispatch-to args t)
690                           )))
691             (push (coerce def 'function) safe-method-names-and-defs)
692             (push method-name safe-method-names-and-defs)))
693      (apply #'java::%jnew-proxy  interface safe-method-names-and-defs))))
694
695
Note: See TracBrowser for help on using the repository browser.