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

Last change on this file since 15086 was 15086, checked in by Mark Evenson, 6 years ago

jss: new macro WITH-CLASS-LOOKUP-DISAMBIGUATED
(Alan Ruttenberg)

(with-class-lookup-disambiguated (lang.object) (find-java-class 'object))

-> success (otherwise error: ambiguous)

From <https://github.com/armedbear/abcl/pull/57>.

Merges
<https://github.com/armedbear/abcl/pull/57/commits/5103d57822691d74c66b80c754f8438df6806bba>.

File size: 28.1 KB
Line 
1;; Copyright (C) 2005 Alan Ruttenberg
2;; Copyright (C) 2011-2 Mark Evenson
3;;
4;; Since JSS 1.0 was largely derivative of the Jscheme System, the
5;; current system is 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 *muffle-warnings* t)
127
128(defvar *imports-resolved-classes* (make-hash-table :test 'equalp))
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 (string 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      (if (or (find #\. name) (find #\{ name))
211          (jss-transform-to-field name)
212          (let ((object-var (gensym))
213                (args-var (gensym)))
214            `(lambda (,object-var &rest ,args-var) 
215               (invoke-restargs ,name  ,object-var ,args-var ,(eql arg 0)))))))
216  (set-dispatch-macro-character #\# #\" 'read-invoke))
217
218(defmacro with-constant-signature (fname-jname-pairs &body body)
219  "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.
220
221FNAME-JNAME-PAIRS is a list of (symbol function &optional raw)
222elements where symbol will be the symbol bound to the method named by
223the string function.  If the optional parameter raw is non-nil, the
224result will be the raw JVM object, uncoerced by the usual conventions.
225
226Use this macro if you are making a lot of calls and
227want to avoid the overhead of the dynamic dispatch."
228
229  (if (null fname-jname-pairs)
230      `(progn ,@body)
231      (destructuring-bind ((fname jname &optional raw) &rest ignore) fname-jname-pairs
232        (declare (ignore ignore))
233        (let ((varname (gensym)))
234          `(let ((,varname nil))
235             (macrolet ((,fname (&rest args)
236                          `(if ,',varname
237                               (if ,',raw
238                                   (jcall-raw ,',varname ,@args)
239                                   (jcall ,',varname ,@args))
240                               (progn
241                                 (setq ,',varname (invoke-find-method ,',jname ,(car args) (list ,@(rest args))))
242                                 (if ,',raw
243                                     (jcall-raw ,',varname ,@args)
244                                     (jcall ,',varname ,@args))))))
245               (with-constant-signature ,(cdr fname-jname-pairs)
246                 ,@body)))))))
247
248(defvar *class-lookup-overrides*)
249
250(defmacro with-class-lookup-disambiguated (overrides &body body)
251  "Suppose you have code that references class using the symbol 'object, and this is ambiguous. E.g. in my system java.lang.Object, org.omg.CORBA.Object. Use (with-class-lookup-disambiguated (lang.object) ...). Within dynamic scope, find-java-class first sees if any of these match, and if so uses them to lookup the class."
252  `(let ((*class-lookup-overrides* ',overrides))
253     ,@body))
254
255(defun maybe-found-in-overridden (name)
256  (when (boundp '*class-lookup-overrides*)
257      (let ((found (find-if (lambda(el) (#"matches" (string el) (concatenate 'string "(?i).*" (string name) "$")))
258          *class-lookup-overrides*)))
259  (if found
260      (let ((*class-lookup-overrides* nil))
261        (lookup-class-name found))))))
262
263
264(defun lookup-class-name (name &key
265                                 (table *class-name-to-full-case-insensitive*)
266                                 (muffle-warning nil)
267                                 (return-ambiguous nil))
268  (let ((overridden (maybe-found-in-overridden name)))
269    (when overridden (return-from lookup-class-name overridden)))
270  (setq name (string name))
271  (let* (;; cant (last-name-pattern (#"compile" '|java.util.regex.Pattern| ".*?([^.]*)$"))
272         ;; reason: bootstrap - the class name would have to be looked up...
273         (last-name-pattern (load-time-value (jstatic (jmethod "java.util.regex.Pattern" "compile"
274                                                               (jclass "java.lang.String"))
275                                                      (jclass "java.util.regex.Pattern") 
276                                                      ".*?([^.]*)$")))
277         (last-name 
278          (let ((matcher (#0"matcher" last-name-pattern name)))
279            (#"matches" matcher)
280            (#"group" matcher 1))))
281    (let* ((bucket (gethash last-name *class-name-to-full-case-insensitive*))
282           (bucket-length (length bucket)))
283      (or (find name bucket :test 'equalp)
284          (flet ((matches-end (end full test)
285                   (= (+ (or (search end full :from-end t :test test) -10)
286                         (length end))
287                      (length full)))
288                 (ambiguous (choices)
289       (if return-ambiguous 
290           (return-from lookup-class-name choices)
291           (error "Ambiguous class name: ~a can be ~{~a~^, ~}" name choices))))
292            (if (zerop bucket-length)
293    (progn (unless muffle-warning (warn "can't find class named ~a" name)) nil)
294                (let ((matches (loop for el in bucket when (matches-end name el 'char=) collect el)))
295                  (if (= (length matches) 1)
296                      (car matches)
297                      (if (= (length matches) 0)
298                          (let ((matches (loop for el in bucket when (matches-end name el 'char-equal) collect el)))
299                            (if (= (length matches) 1)
300                                (car matches)
301                                (if (= (length matches) 0)
302            (progn (unless muffle-warning (warn "can't find class named ~a" name)) nil)
303                                    (ambiguous matches))))
304                          (ambiguous matches))))))))))
305
306(defun get-all-jar-classnames (jar-file-name)
307  (let* ((jar (jnew (jconstructor "java.util.jar.JarFile" (jclass "java.lang.String")) (namestring (truename jar-file-name))))
308         (entries (#"entries" jar)))
309    (with-constant-signature ((matcher "matcher" t) (substring "substring")
310                              (jreplace "replace" t) (jlength "length")
311                              (matches "matches") (getname "getName" t)
312                              (next "nextElement" t) (hasmore "hasMoreElements")
313                              (group "group"))
314      (loop while (hasmore entries)
315         for name =  (getname (next entries))
316         with class-pattern = (jstatic "compile" "java.util.regex.Pattern" ".*\\.class$")
317         with name-pattern = (jstatic "compile" "java.util.regex.Pattern" ".*?([^.]*)$")
318         when (matches (matcher class-pattern name))
319         collect
320           (let* ((fullname (substring (jreplace name #\/ #\.) 0 (- (jlength name) 6)))
321                  (matcher (matcher name-pattern fullname))
322                  (name (progn (matches matcher) (group matcher 1))))
323             (cons name fullname))
324           ))))
325
326(defun jar-import (file)
327  "Import all the Java classes contained in the pathname FILE into the JSS dynamic lookup cache."
328  (when (probe-file file)
329    (loop for (name . full-class-name) in (get-all-jar-classnames file)
330       do 
331         (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
332                  :test 'equal))))
333
334(defun new (class-name &rest args)
335  "Invoke the Java constructor for CLASS-NAME with ARGS.
336
337CLASS-NAME may either be a symbol or a string according to the usual JSS conventions."
338  (invoke-restargs 'new class-name args))
339
340(defvar *running-in-osgi* (ignore-errors (jclass "org.osgi.framework.BundleActivator")))
341
342(define-condition no-such-java-field (error)
343  ((field-name
344    :initarg :field-name
345    :reader field-name
346    )
347   (object
348    :initarg :object
349    :reader object
350    ))
351  (:report (lambda (c stream)
352             (format stream "Unable to find a FIELD named ~a for ~a"
353                     (field-name c) (object c))))
354  )
355
356(defun get-java-field (object field &optional (try-harder *running-in-osgi*))
357  "Get the value of the FIELD contained in OBJECT.
358If OBJECT is a symbol it names a dot qualified static FIELD."
359  (if try-harder
360      (let* ((class (if (symbolp object)
361                        (setq object (find-java-class object))
362                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)))
363                            object
364                            (jobject-class object))))
365             (jfield (if (java-object-p field)
366                         field
367                         (or (find-declared-field field class)
368                             (error 'no-such-java-field :field-name field :object object)))))
369        (#"setAccessible" jfield +true+)
370        (values (#"get" jfield object) jfield))
371      (if (symbolp object)
372          (let ((class (find-java-class object)))
373            (jfield class field))
374          (jfield field object))))
375
376(defun find-declared-field (field class)
377  "Return a FIELD object corresponding to the definition of FIELD
378\(a string\) visible at CLASS. *Not* restricted to public classes, and checks
379all superclasses of CLASS.
380   Returns NIL if no field object is found."
381  (loop while class
382     for field-obj = (get-declared-field class field)
383     if field-obj
384     do (return-from find-declared-field field-obj)
385     else
386     do (setf class (jclass-superclass class)))
387  nil)
388
389(defun get-declared-field (class fieldname)
390  (find fieldname (#"getDeclaredFields" class)
391        :key 'jfield-name :test 'equal))
392
393;; TODO use #"getSuperclass" and #"getInterfaces" to see whether there
394;; are fields in superclasses that we might set
395(defun set-java-field (object field value &optional (try-harder *running-in-osgi*))
396  "Set the FIELD of OBJECT to VALUE.
397If OBJECT is a symbol, it names a dot qualified Java class to look for
398a static FIELD.  If OBJECT is an instance of java:java-object, the
399associated is used to look up the static FIELD."
400  (if try-harder
401      (let* ((class (if (symbolp object)
402                        (setq object (find-java-class object))
403                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)) )
404                            object
405                            (jobject-class object))))
406             (jfield (if (java-object-p field)
407                         field
408                         (or (find-declared-field field class)
409                             (error 'no-such-java-field :field-name field :object object)))))
410        (#"setAccessible" jfield +true+)
411        (values (#"set" jfield object value) jfield))
412      (if (symbolp object)
413          (let ((class (find-java-class object)))
414            (setf (jfield (#"getName" class) field) value))
415          (if (typep object 'java-object)
416              (setf (jfield (jclass-of object) field) value)
417              (setf (jfield object field) value)))))
418
419(defun (setf get-java-field) (value object field &optional (try-harder *running-in-osgi*))
420  (set-java-field object field value try-harder))
421
422
423(defconstant +for-name+ 
424  (jmethod "java.lang.Class" "forName" "java.lang.String" "boolean" "java.lang.ClassLoader"))
425
426(defun find-java-class (name)
427  (or (jstatic +for-name+ "java.lang.Class" 
428               (maybe-resolve-class-against-imports name) +true+ java::*classloader*)
429      (ignore-errors (jclass (maybe-resolve-class-against-imports name)))))
430
431(defmethod print-object ((obj (jclass "java.lang.Class")) stream) 
432  (print-unreadable-object (obj stream :identity nil)
433    (format stream "java class ~a" (jclass-name obj))))
434
435(defmethod print-object ((obj (jclass "java.lang.reflect.Method")) stream) 
436  (print-unreadable-object (obj stream :identity nil)
437    (format stream "method ~a" (#"toString" obj))))
438
439(defun do-auto-imports ()
440  (labels ((expand-paths (cp)
441             (loop :for s :in cp
442                :appending (loop :for entry 
443                              :in (let ((p (pathname s)))
444                                    (if (wild-pathname-p p)
445                                        (directory p)
446                                        (list p)))
447                              :collecting entry)))
448           (import-classpath (cp)
449             (mapcar 
450              (lambda (p) 
451                (when *load-verbose*
452                  (format t ";; Importing ~A~%" p))
453                (cond 
454                  ((file-directory-p p) )
455                  ((equal (pathname-type p) "jar")
456                   (jar-import (merge-pathnames p
457                                                (format nil "~a/" (jstatic "getProperty" "java.lang.System" "user.dir")))))))
458              cp))
459           (split-classpath (cp)
460             (coerce 
461              (jcall "split" cp 
462                     (string (jfield (jclass "java.io.File") "pathSeparatorChar")))
463              'cons))
464           (do-imports (cp)
465             (import-classpath (expand-paths (split-classpath cp)))))
466    (do-imports (jcall "getClassPath" (jstatic "getRuntimeMXBean" '|java.lang.management.ManagementFactory|)))
467    (do-imports (jcall "getBootClassPath" (jstatic "getRuntimeMXBean" '|java.lang.management.ManagementFactory|)))))
468
469(eval-when (:load-toplevel :execute)
470  (when *do-auto-imports* 
471    (do-auto-imports)))
472
473(defun japropos (string)
474  "Output the names of all Java class names loaded in the current process which match STRING.."
475  (setq string (string string))
476  (let ((matches nil))
477    (maphash (lambda(key value) 
478               (declare (ignore key))
479               (loop for class in value
480                  when (search string class :test 'string-equal)
481                  do (pushnew (list class "Java Class") matches :test 'equal)))
482             *class-name-to-full-case-insensitive*)
483    (loop for (match type) in (sort matches 'string-lessp :key 'car)
484       do (format t "~a: ~a~%" match type))
485    ))
486
487(defun jclass-method-names (class &optional full)
488  (if (java-object-p class)
489      (if (equal (jclass-name (jobject-class class)) "java.lang.Class")
490          (setq class (jclass-name class))
491          (setq class (jclass-name (jobject-class class)))))
492  (union
493   (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getMethods" (find-java-class class))) :test 'equal)
494   (ignore-errors (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getConstructors" (find-java-class class))) :test 'equal))))
495
496(defun java-class-method-names (class &optional stream)
497  "Return a list of the public methods encapsulated by the JVM CLASS.
498
499If STREAM non-nil, output a verbose description to the named output stream.
500
501CLASS may either be a string naming a fully qualified JVM class in dot
502notation, or a symbol resolved against all class entries in the
503current classpath."
504  (if stream
505      (dolist (method (jclass-method-names class t))
506        (format stream "~a~%" method))
507      (jclass-method-names class)))
508
509(setf (symbol-function 'jcmn) #'java-class-method-names)
510
511(defun path-to-class (classname)
512  (let ((full (lookup-class-name classname)))
513    (#"toString" 
514     (#"getResource" 
515      (find-java-class full)
516      (concatenate 'string "/" (substitute #\/ #\. full) ".class")))))
517
518;; http://www.javaworld.com/javaworld/javaqa/2003-07/02-qa-0725-classsrc2.html
519
520(defun all-loaded-classes ()
521  (let ((classes-field 
522         (find "classes" (#"getDeclaredFields" (jclass "java.lang.ClassLoader"))
523               :key #"getName" :test 'equal)))
524    (#"setAccessible" classes-field +true+)
525    (loop for classloader in (mapcar #'first (dump-classpath))
526       append
527         (loop with classesv = (#"get" classes-field classloader)
528            for i below (#"size" classesv)
529            collect (#"getName" (#"elementAt" classesv i)))
530       append
531         (loop with classesv = (#"get" classes-field (#"getParent" classloader))
532            for i below (#"size" classesv)
533            collect (#"getName" (#"elementAt" classesv i))))))
534
535(defun get-dynamic-class-path ()
536  (rest 
537   (find-if (lambda (loader) 
538              (string= "org.armedbear.lisp.JavaClassLoader"
539                       (jclass-name (jobject-class loader))))
540            (dump-classpath)
541            :key #'car)))
542
543(defun java-gc ()
544  (#"gc" (#"getRuntime" 'java.lang.runtime))
545  (#"runFinalization" (#"getRuntime" 'java.lang.runtime))
546  (#"gc" (#"getRuntime" 'java.lang.runtime))
547  (java-room))
548
549(defun java-room ()
550  (let ((rt (#"getRuntime" 'java.lang.runtime)))
551    (values (- (#"totalMemory" rt) (#"freeMemory" rt))
552            (#"totalMemory" rt)
553            (#"freeMemory" rt)
554            (list :used :total :free))))
555
556(defun verbose-gc (&optional (new-value nil new-value-supplied))
557  (if new-value-supplied
558      (progn (#"setVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory) new-value) new-value)
559      (#"isVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory))))
560
561(defun all-jars-below (directory) 
562  (loop with q = (system:list-directory directory) 
563     while q for top = (pop q)
564     if (null (pathname-name top)) do (setq q (append q (all-jars-below top))) 
565     if (equal (pathname-type top) "jar") collect top))
566
567(defun all-classfiles-below (directory) 
568  (loop with q = (system:list-directory directory) 
569     while q for top = (pop q)
570     if (null (pathname-name top)) do (setq q (append q (all-classfiles-below top ))) 
571     if (equal (pathname-type top) "class")
572     collect top
573       ))
574
575(defun all-classes-below-directory (directory)
576  (loop for file in (all-classfiles-below directory) collect
577       (format nil "~{~a.~}~a"
578               (subseq (pathname-directory file) (length (pathname-directory directory)))
579               (pathname-name file))
580       ))
581
582(defun classfiles-import (directory)
583  "Load all Java classes recursively contained under DIRECTORY in the current process."
584  (setq directory (truename directory))
585  (loop for full-class-name in (all-classes-below-directory directory)
586     for name = (#"replaceAll" full-class-name "^.*\\." "")
587     do
588       (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
589                :test 'equal)))
590
591(defun jclass-all-interfaces (class)
592  "Return a list of interfaces the class implements"
593  (unless (java-object-p class)
594    (setq class (find-java-class class)))
595  (loop for aclass = class then (#"getSuperclass" aclass)
596     while aclass
597     append (coerce (#"getInterfaces" aclass) 'list)))
598
599(defun safely (f name)
600  (let ((fname (gensym)))
601    (compile fname
602             `(lambda(&rest args)
603                (with-simple-restart (top-level
604                                      "Return from lisp method implementation for ~a." ,name)
605                  (apply ,f args))))
606    (symbol-function fname)))
607
608(defun jdelegating-interface-implementation (interface dispatch-to &rest method-names-and-defs)
609  "Creates and returns an implementation of a Java interface with
610   methods calling Lisp closures as given in METHOD-NAMES-AND-DEFS.
611
612   INTERFACE is an interface
613
614   DISPATCH-TO is an existing Java object
615
616   METHOD-NAMES-AND-DEFS is an alternating list of method names
617   (strings) and method definitions (closures).
618
619   For missing methods, a dummy implementation is provided that
620   calls the method on DISPATCH-TO."
621  (let ((implemented-methods
622         (loop for m in method-names-and-defs
623            for i from 0
624            if (evenp i) 
625            do (assert (stringp m) (m) "Method names must be strings: ~s" m) and collect m
626            else
627            do (assert (or (symbolp m) (functionp m)) (m) "Methods must be function designators: ~s" m))))
628    (let ((safe-method-names-and-defs 
629           (loop for (name function) on method-names-and-defs by #'cddr
630              collect name collect (safely function name))))
631      (loop for method across
632           (jclass-methods interface :declared nil :public t)
633         for method-name = (jmethod-name method)
634         when (not (member method-name implemented-methods :test #'string=))
635         do
636           (let* ((def  `(lambda
637                             (&rest args)
638                           (invoke-restargs ,(jmethod-name method) ,dispatch-to args t)
639                           )))
640             (push (coerce def 'function) safe-method-names-and-defs)
641             (push method-name safe-method-names-and-defs)))
642      (apply #'java::%jnew-proxy  interface safe-method-names-and-defs))))
643
644
Note: See TracBrowser for help on using the repository browser.