source: branches/1.1.x/contrib/jss/invoke.lisp

Last change on this file was 14234, checked in by Mark Evenson, 12 years ago

Fixes #263: ABCL-CONTRIB now loads with wildcards in CLASSPATH.

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