source: trunk/abcl/contrib/jss/invoke.lisp @ 13910

Last change on this file since 13910 was 13910, checked in by Mark Evenson, 9 years ago

jss: add docstring for the rather useful HASHMAP-TO-HASHTABLE.

File size: 25.3 KB
Line 
1;; invoke.lisp v2.0
2;;
3;; Copyright (C) 2005 Alan Ruttenberg
4;; Copyright (C) 2011 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
127(defvar *imports-resolved-classes* (make-hash-table :test 'equal))
128
129(defun find-java-class (name)
130  (jclass (maybe-resolve-class-against-imports name)))
131
132(defmacro invoke-add-imports (&rest imports)
133  "Push these imports onto the search path. If multiple, earlier in list take precedence"
134  `(eval-when (:compile-toplevel :load-toplevel :execute)
135     (clrhash *imports-resolved-classes*)
136     (dolist (i (reverse ',imports))
137       (setq *imports-resolved-classes* (delete i *imports-resolved-classes* :test 'equal))
138       )))
139
140(defun clear-invoke-imports ()
141  (clrhash *imports-resolved-classes*))
142
143(defun maybe-resolve-class-against-imports (classname)
144  (or (gethash classname *imports-resolved-classes*)
145      (let ((found (lookup-class-name classname)))
146  (if found
147      (progn 
148        (setf (gethash classname *imports-resolved-classes*) found)
149        found)
150      (string classname)))))
151
152(defvar *class-name-to-full-case-insensitive* (make-hash-table :test 'equalp))
153
154;; This is the function that calls invoke to call your java
155;; method. The first argument is the method name or 'new. The second
156;; is the object you are calling it on, followed by the rest of the
157;; arguments. If the "object" is a symbol, then that symbol is assumed
158;; to be a java class, and a static method on the class is called,
159;; otherwise a regular method is called.
160
161(defun invoke (method object &rest args)
162    (invoke-restargs method object args))
163
164(defun invoke-restargs (method object args &optional (raw? nil))
165  (let* ((object-as-class-name 
166          (if (symbolp object) (maybe-resolve-class-against-imports object)))
167         (object-as-class 
168          (if object-as-class-name (find-java-class object-as-class-name))))
169    (if (eq method 'new)
170        (apply #'jnew (or object-as-class-name object) args)
171        (if raw?
172            (if (symbolp object)
173                (apply #'jstatic-raw method object-as-class  args)
174                (apply #'jcall-raw method object  args))
175            (if (symbolp object)
176                (apply #'jstatic method object-as-class args)
177                (apply #'jcall method object args))))))
178
179;;; Method name as String --> String  | Symbol --> jmethod
180(defvar *methods-cache* (make-hash-table :test #'equal))
181
182(defun get-jmethod (method object) 
183  (when (gethash method *methods-cache*)
184    (gethash 
185     (if (symbolp object) (lookup-class-name object) (jobject-class object))
186     (gethash method *methods-cache*))))
187
188(defun set-jmethod (method object jmethod) 
189  (unless (gethash method *methods-cache*)
190    (setf (gethash method *methods-cache*) (make-hash-table :test #'equal)))
191  (setf 
192   (gethash 
193    (if (symbolp object) (lookup-class-name object) (jobject-class object))
194    (gethash method *methods-cache*))
195   jmethod))
196
197(defconstant +set-accessible+ 
198  (jmethod "java.lang.reflect.AccessibleObject" "setAccessible" "boolean"))
199
200;;; TODO optimize me!
201(defun invoke-find-method (method object args)
202  (let ((jmethod (get-jmethod method object)))
203    (unless jmethod
204      (setf jmethod 
205            (if (symbolp object)
206                ;;; static method
207                (apply #'jmethod (lookup-class-name object) 
208                       method (mapcar #'jobject-class args))
209                  ;;; instance method
210                (apply #'jresolve-method 
211                       method object args)))
212      (jcall +set-accessible+ jmethod +true+)
213      (set-jmethod method object jmethod))
214    jmethod))
215
216;; This is the reader macro for java methods. it translates the method
217;; into a lambda form that calls invoke. Which is nice because you
218;; can, e.g. do this: (mapcar #"toString" list-of-java-objects). The reader
219;; macro takes one arg. If 0, then jstatic-raw is called, so that abcl doesn't
220;; automagically convert the returned java object into a lisp object. So
221;; #0"toString" returns a java.lang.String object, where as #"toString" returns
222;; a regular Lisp string as ABCL converts the Java string to a Lisp string.
223
224(eval-when (:compile-toplevel :load-toplevel :execute)
225  (defun read-invoke (stream char arg) 
226    (unread-char char stream)
227    (let ((name (read stream)))
228      (let ((object-var (gensym))
229            (args-var (gensym)))
230        `(lambda (,object-var &rest ,args-var) 
231           (invoke-restargs ,name  ,object-var ,args-var ,(eql arg 0))))))
232  (set-dispatch-macro-character #\# #\" 'read-invoke))
233
234(defmacro with-constant-signature (fname-jname-pairs &body body)
235  (if (null fname-jname-pairs)
236      `(progn ,@body)
237      (destructuring-bind ((fname jname &optional raw) &rest ignore) fname-jname-pairs
238  (declare (ignore ignore))
239  (let ((varname (gensym)))
240    `(let ((,varname nil))
241       (macrolet ((,fname (&rest args)
242        `(if ,',varname
243             (if ,',raw
244           (jcall-raw ,',varname ,@args)
245           (jcall ,',varname ,@args))
246             (progn
247         (setq ,',varname (invoke-find-method ,',jname ,(car args) (list ,@(rest args))))
248         (if ,',raw
249             (jcall-raw ,',varname ,@args)
250             (jcall ,',varname ,@args))))))
251         (with-constant-signature ,(cdr fname-jname-pairs)
252     ,@body)))))))
253
254(defun lookup-class-name (name)
255  (setq name (string name))
256  (let* (;; cant (last-name-pattern (#"compile" '|java.util.regex.Pattern| ".*?([^.]*)$"))
257   ;; reason: bootstrap - the class name would have to be looked up...
258   (last-name-pattern (load-time-value (jstatic (jmethod "java.util.regex.Pattern" "compile"
259                     (jclass "java.lang.String"))
260                  (jclass "java.util.regex.Pattern") 
261                  ".*?([^.]*)$")))
262
263   (last-name 
264    (let ((matcher (#0"matcher" last-name-pattern name)))
265      (#"matches" matcher)
266      (#"group" matcher 1))))
267    (let* ((bucket (gethash last-name *class-name-to-full-case-insensitive*))
268     (bucket-length (length bucket)))
269      (or (find name bucket :test 'equalp)
270    (flet ((matches-end (end full test)
271       (= (+ (or (search end full :from-end t :test test) -10)
272       (length end))
273          (length full)))
274     (ambiguous (choices)
275       (error "Ambiguous class name: ~a can be ~{~a~^, ~}" name choices)))
276      (if (zerop bucket-length)
277    name
278    (let ((matches (loop for el in bucket when (matches-end name el 'char=) collect el)))
279      (if (= (length matches) 1)
280          (car matches)
281          (if (= (length matches) 0)
282        (let ((matches (loop for el in bucket when (matches-end name el 'char-equal) collect el)))
283          (if (= (length matches) 1)
284        (car matches)
285        (if (= (length matches) 0)
286            name
287            (ambiguous matches))))
288        (ambiguous matches))))))))))
289
290(defun get-all-jar-classnames (jar-file-name)
291  (let* ((jar (jnew (jconstructor "java.util.jar.JarFile" (jclass "java.lang.String")) (namestring (truename jar-file-name))))
292         (entries (#"entries" jar)))
293    (with-constant-signature ((matcher "matcher" t) (substring "substring")
294            (jreplace "replace" t) (jlength "length")
295            (matches "matches") (getname "getName" t)
296            (next "nextElement" t) (hasmore "hasMoreElements")
297            (group "group"))
298      (loop while (hasmore entries)
299   for name =  (getname (next entries))
300   with class-pattern = (#"compile" '|java.util.regex.Pattern| "[^$]*\\.class$")
301   with name-pattern = (#"compile" '|java.util.regex.Pattern| ".*?([^.]*)$")
302   when (matches (matcher class-pattern name))
303   collect
304     (let* ((fullname (substring (jreplace name #\/ #\.) 0 (- (jlength name) 6)))
305      (matcher (matcher name-pattern fullname))
306      (name (progn (matches matcher) (group matcher 1))))
307       (cons name fullname))
308   ))))
309
310(defun jar-import (file)
311  (when (probe-file file)
312    (loop for (name . full-class-name) in (get-all-jar-classnames file)
313       do 
314   (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
315      :test 'equal))))
316
317(defun new (class-name &rest args)
318  (invoke-restargs 'new class-name args))
319
320(defvar *running-in-osgi* (ignore-errors (jclass "org.osgi.framework.BundleActivator")))
321
322(defun get-java-field (object field &optional (try-harder *running-in-osgi*))
323  "Get the value of the FIELD contained in OBJECT.
324If OBJECT is a symbol it names a dot qualified static FIELD."
325  (if try-harder
326      (let* ((class (if (symbolp object)
327      (setq object (find-java-class object))
328                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)))
329                            object
330                            (jobject-class object))))
331       (jfield (if (java-object-p field)
332       field
333                         (find field (#"getDeclaredFields" class) 
334                               :key 'jfield-name :test 'equal))))
335  (#"setAccessible" jfield t)
336  (values (#"get" jfield object) jfield))
337      (if (symbolp object)
338          (let ((class (find-java-class object)))
339            (jfield class field))
340          (jfield field object))))
341
342;; TODO use #"getSuperclass" and #"getInterfaces" to see whether there
343;; are fields in superclasses that we might set
344(defun set-java-field (object field value &optional (try-harder *running-in-osgi*))
345  "Set the FIELD of OBJECT to VALUE.
346If OBJECT is a symbol, it names a dot qualified Java class to look for
347a static FIELD.  If OBJECT is an instance of java:java-object, the
348associated is used to look up the static FIELD."
349  (if try-harder
350      (let* ((class (if (symbolp object)
351      (setq object (find-java-class object))
352          (if (equal "java.lang.Class" (jclass-name (jobject-class object)) )
353        object
354      (jobject-class object))))
355       (jfield (if (java-object-p field)
356       field
357           (find field (#"getDeclaredFields" class) :key 'jfield-name :test 'equal))))
358  (#"setAccessible" jfield t)
359  (values (#"set" jfield object value) jfield))
360    (if (symbolp object)
361  (let ((class (find-java-class object)))
362          (setf (jfield (#"getName" class) field) value))
363        (if (typep object 'java-object)
364            (setf (jfield (jclass-of object) field) value)
365            (setf (jfield object field) value)))))
366
367
368(defconstant +for-name+ 
369  (jmethod "java.lang.Class" "forName" "java.lang.String" "boolean" "java.lang.ClassLoader"))
370
371(defun find-java-class (name)
372  (or (jstatic +for-name+ "java.lang.Class" 
373               (maybe-resolve-class-against-imports name) +true+ java::*classloader*)
374      (ignore-errors (jclass (maybe-resolve-class-against-imports name)))))
375
376(defmethod print-object ((obj (jclass "java.lang.Class")) stream) 
377  (print-unreadable-object (obj stream :identity nil)
378    (format stream "java class ~a" (jclass-name obj))))
379
380(defmethod print-object ((obj (jclass "java.lang.reflect.Method")) stream) 
381  (print-unreadable-object (obj stream :identity nil)
382    (format stream "method ~a" (#"toString" obj))))
383
384(defun do-auto-imports ()
385  (flet ((import-class-path (cp)
386     (map nil
387    (lambda(s) 
388      (setq s (jcall "toString" s))
389      (when *load-verbose*
390        (format t ";Importing ~a~%" s))
391      (cond 
392        ((file-directory-p s) )
393        ((equal (pathname-type s) "jar")
394         (jar-import (merge-pathnames (jcall "toString" s) 
395                                                  (format nil "~a/" (jstatic "getProperty" "java.lang.System" "user.dir")))))))
396    (jcall "split" cp 
397                       (string (jfield (jclass "java.io.File") "pathSeparatorChar"))))))
398    (import-class-path (jcall "getClassPath" (jstatic "getRuntimeMXBean" '|java.lang.management.ManagementFactory|)))
399    (import-class-path (jcall "getBootClassPath" (jstatic "getRuntimeMXBean" '|java.lang.management.ManagementFactory|)))
400    ))
401
402(eval-when (:load-toplevel :execute)
403  (when *do-auto-imports* 
404    (do-auto-imports)))
405
406(defun japropos (string)
407  (setq string (string string))
408  (let ((matches nil))
409    (maphash (lambda(key value) 
410         (declare (ignore key))
411         (loop for class in value
412      when (search string class :test 'string-equal)
413        do (pushnew (list class "Java Class") matches :test 'equal)))
414       *class-name-to-full-case-insensitive*)
415    (loop for (match type) in (sort matches 'string-lessp :key 'car)
416   do (format t "~a: ~a~%" match type))
417    ))
418
419(defun jclass-method-names (class &optional full)
420  (if (java-object-p class)
421      (if (equal (jclass-name (jobject-class class)) "java.lang.Class")
422    (setq class (jclass-name class))
423    (setq class (jclass-name (jobject-class class)))))
424  (union
425   (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getMethods" (find-java-class class))) :test 'equal)
426   (ignore-errors (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getConstructors" (find-java-class class))) :test 'equal))))
427
428(defun jcmn (class &optional full)
429  (if full 
430      (dolist (method (jclass-method-names class t))
431  (format t "~a~%" method))
432      (jclass-method-names class)))
433
434(defun path-to-class (classname)
435  (let ((full (lookup-class-name classname)))
436    (#"toString" 
437     (#"getResource" 
438      (find-java-class full)
439      (concatenate 'string "/" (substitute #\/ #\. full) ".class")))))
440
441;; http://www.javaworld.com/javaworld/javaqa/2003-07/02-qa-0725-classsrc2.html
442
443(defun all-loaded-classes ()
444  (let ((classes-field 
445   (find "classes" (#"getDeclaredFields" (jclass "java.lang.ClassLoader"))
446         :key #"getName" :test 'equal)))
447    (#"setAccessible" classes-field t)
448    (loop for classloader in (mapcar #'first (dump-classpath))
449   append
450   (loop with classesv = (#"get" classes-field classloader)
451      for i below (#"size" classesv)
452      collect (#"getName" (#"elementAt" classesv i)))
453   append
454   (loop with classesv = (#"get" classes-field (#"getParent" classloader))
455      for i below (#"size" classesv)
456      collect (#"getName" (#"elementAt" classesv i))))))
457   
458(defun get-dynamic-class-path ()
459  (rest 
460   (find-if (lambda (loader) 
461              (string= "org.armedbear.lisp.JavaClassLoader"
462                       (jclass-name (jobject-class loader))))
463            (dump-classpath)
464            :key #'car)))
465
466(defun java-gc ()
467  (#"gc" (#"getRuntime" 'java.lang.runtime))
468  (#"runFinalization" (#"getRuntime" 'java.lang.runtime))
469  (#"gc" (#"getRuntime" 'java.lang.runtime))
470  (java-room))
471
472(defun java-room ()
473  (let ((rt (#"getRuntime" 'java.lang.runtime)))
474    (values (- (#"totalMemory" rt) (#"freeMemory" rt))
475     (#"totalMemory" rt)
476     (#"freeMemory" rt)
477     (list :used :total :free))))
478
479(defun verbose-gc (&optional (new-value nil new-value-supplied))
480  (if new-value-supplied
481      (progn (#"setVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory) new-value) new-value)
482      (#"isVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory))))
483
484(defun all-jars-below (directory) 
485  (loop with q = (system:list-directory directory) 
486     while q for top = (pop q)
487     if (null (pathname-name top)) do (setq q (append q (all-jars-below top))) 
488     if (equal (pathname-type top) "jar") collect top))
489
490(defun all-classfiles-below (directory) 
491  (loop with q = (system:list-directory directory) 
492     while q for top = (pop q)
493     if (null (pathname-name top)) do (setq q (append q (all-classfiles-below top ))) 
494     if (equal (pathname-type top) "class")
495     collect top
496     ))
497
498(defun all-classes-below-directory (directory)
499  (loop for file in (all-classfiles-below directory) collect
500       (format nil "~{~a.~}~a"
501         (subseq (pathname-directory file) (length (pathname-directory directory)))
502         (pathname-name file))
503       ))
504
505(defun classfiles-import (directory)
506  (setq directory (truename directory))
507  (loop for full-class-name in (all-classes-below-directory directory)
508       for name = (#"replaceAll" full-class-name "^.*\\." "")
509     do
510       (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
511    :test 'equal)))
512
513(defun set-to-list (set)
514  (declare (optimize (speed 3) (safety 0)))
515  (with-constant-signature ((iterator "iterator" t) (hasnext "hasNext") (next "next"))
516    (loop with iterator = (iterator set)
517       while (hasNext iterator)
518       for item = (next iterator)
519       collect item)))
520
521(defun jlist-to-list (list)
522  "Convert a LIST implementing java.util.List to a Lisp list."
523  (declare (optimize (speed 3) (safety 0)))
524  (loop :for i :from 0 :below (jcall "size" list)
525     :collecting (jcall "get" list i)))
526
527(defun jarray-to-list (jarray)
528  (declare (optimize (speed 3) (safety 0)))
529  (jlist-to-list
530   (jstatic "asList" "java.util.Arrays" jarray)))
531
532;;; Deprecated
533;;;
534;;; XXX unclear what sort of list this would actually work on, as it
535;;; certainly doesn't seem to be any of the Java collection types
536;;; (what implements getNext())?
537(defun list-to-list (list)
538  (declare (optimize (speed 3) (safety 0)))
539  (with-constant-signature ((isEmpty "isEmpty") (getfirst "getFirst")
540                            (getNext "getNext"))
541    (loop until (isEmpty list)
542       collect (getFirst list)
543       do (setq list (getNext list)))))
544
545;; Contribution of Luke Hope. (Thanks!)
546
547(defun iterable-to-list (iterable)
548 (declare (optimize (speed 3) (safety 0)))
549 (let ((it (#"iterator" iterable)))
550   (with-constant-signature ((hasmore "hasMoreElements")
551           (next "nextElement"))
552     (loop while (hasmore it)
553  collect (next it)))))
554
555(defun vector-to-list (vector)
556 (declare (optimize (speed 3) (safety 0)))
557 (with-constant-signature ((hasmore "hasMoreElements")
558         (next "nextElement"))
559     (loop while (hasmore vector)
560  collect (next vector))))
561
562(defun hashmap-to-hashtable (hashmap &rest rest &key (keyfun #'identity) (valfun #'identity) (invert? nil)
563            table 
564                             &allow-other-keys )
565  "Converts the a HASHMAP reference to a java.util.HashMap object to a Lisp hashtable.
566
567The REST paramter specifies arguments to the underlying MAKE-HASH-TABLE call.
568
569KEYFUN and VALFUN specifies functions to be run on the keys and values
570of the HASHMAP right before they are placed in the hashtable.
571
572If INVERT? is non-nil than reverse the keys and values in the resulting hashtable."
573  (let ((keyset (#"keySet" hashmap))
574  (table (or table (apply 'make-hash-table
575        (loop for (key value) on rest by #'cddr
576           unless (member key '(:invert? :valfun :keyfun :table)) 
577           collect key and collect value)))))
578    (with-constant-signature ((iterator "iterator" t) (hasnext "hasNext") (next "next"))
579      (loop with iterator = (iterator keyset)
580   while (hasNext iterator)
581   for item = (next iterator)
582   do (if invert?
583    (setf (gethash (funcall valfun (#"get" hashmap item)) table) (funcall keyfun item))
584    (setf (gethash (funcall keyfun item) table) (funcall valfun (#"get" hashmap item)))))
585    table)))
586     
587(defun jclass-all-interfaces (class)
588  "Return a list of interfaces the class implements"
589  (unless (java-object-p class)
590    (setq class (find-java-class class)))
591  (loop for aclass = class then (#"getSuperclass" aclass)
592     while aclass
593     append (coerce (#"getInterfaces" aclass) 'list)))
594
595(defun safely (f name)
596  (let ((fname (gensym)))
597    (compile fname
598       `(lambda(&rest args)
599    (with-simple-restart (top-level
600              "Return from lisp method implementation for ~a." ,name)
601      (apply ,f args))))
602    (symbol-function fname)))
603
604(defun jdelegating-interface-implementation (interface dispatch-to &rest method-names-and-defs)
605  "Creates and returns an implementation of a Java interface with
606   methods calling Lisp closures as given in METHOD-NAMES-AND-DEFS.
607
608   INTERFACE is an interface
609
610   DISPATCH-TO is an existing Java object
611
612   METHOD-NAMES-AND-DEFS is an alternating list of method names
613   (strings) and method definitions (closures).
614
615   For missing methods, a dummy implementation is provided that
616   calls the method on DISPATCH-TO."
617  (let ((implemented-methods
618         (loop for m in method-names-and-defs
619      for i from 0
620      if (evenp i) 
621      do (assert (stringp m) (m) "Method names must be strings: ~s" m) and collect m
622      else
623      do (assert (or (symbolp m) (functionp m)) (m) "Methods must be function designators: ~s" m))))
624    (let ((safe-method-names-and-defs 
625     (loop for (name function) on method-names-and-defs by #'cddr
626        collect name collect (safely function name))))
627      (loop for method across
628     (jclass-methods interface :declared nil :public t)
629     for method-name = (jmethod-name method)
630     when (not (member method-name implemented-methods :test #'string=))
631     do
632     (let* ((def  `(lambda
633           (&rest args)
634         (invoke-restargs ,(jmethod-name method) ,dispatch-to args t)
635         )))
636       (push (coerce def 'function) safe-method-names-and-defs)
637       (push method-name safe-method-names-and-defs)))
638      (apply #'java::%jnew-proxy  interface safe-method-names-and-defs))))
639
640
Note: See TracBrowser for help on using the repository browser.