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

Last change on this file was 15725, checked in by Mark Evenson, 10 months ago

jss: improve GET-JAVA-FIELD docstring

File size: 31.8 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;;      In progress with jss-3.5.0's JSS:MAP
93;;   - write loop clauses for java collections.
94;;   - Register classes in .class files below classpath directories (when :wild-inferiors works)
95;;   - Make documentation like Edi Weitz
96;;
97;; Thanks: Peter Graves, Jscheme developers, Mike Travers for skij, 
98;; Andras Simon for jfli-abcl which bootstrapped me and taught me how to do
99;; get-all-jar-classnames
100;;
101
102;; changelog
103
104;; Sat January 28, 2006, alanr:
105
106;; Change imports strategy. Only index by last part of class name,
107;; case insensitive. Make the lookup-class-name logic be a bit more
108;; complicated. This substantially reduces the time it takes to do the
109;; auto imports and since class name lookup is relatively infrequent,
110;; and in any case cached, this doesn't effect run time speed.  (did
111;; try caching, but didn't pay - more time was spent reading and
112;; populating large hash table)
113;;
114;; Split class path by ";" in addition to ":" for windows.
115;;
116;; Tested on windows, linux.
117
118;; 2011-05-21 Mark Evenson
119;;   "ported" to native ABCL without needing the jscheme.jar or bsh-2.0b4.jar
120
121(in-package :jss)
122
123(eval-when (:compile-toplevel :load-toplevel :execute)
124  (defvar *do-auto-imports* t 
125    "Whether to automatically introspect all Java classes on the classpath when JSS is loaded.")
126  (defvar *muffle-warnings* t
127    "Attempt to make JSS less chatting about how things are going.")
128  (defvar *imports-resolved-classes* (make-hash-table :test 'equalp)
129    "Hashtable of all resolved imports by the current process."))
130
131(defun find-java-class (name)
132  "Returns the java.lang.Class representation of NAME.
133
134NAME can either string or a symbol according to the usual JSS conventions."
135  (jclass (maybe-resolve-class-against-imports name)))
136
137(defmacro invoke-add-imports (&rest imports)
138  "Push these imports onto the search path. If multiple, earlier in list take precedence"
139  `(eval-when (:compile-toplevel :load-toplevel :execute)
140     (clrhash *imports-resolved-classes*)
141     (dolist (i (reverse ',imports))
142       (setq *imports-resolved-classes* (delete i *imports-resolved-classes* :test 'equal))
143       )))
144
145(defun clear-invoke-imports ()
146  (clrhash *imports-resolved-classes*))
147
148(defun maybe-resolve-class-against-imports (classname)
149  (or (gethash (string classname) *imports-resolved-classes*)
150      (let ((found (lookup-class-name classname)))
151        (if found
152            (progn 
153              (setf (gethash classname *imports-resolved-classes*) found)
154              found)
155            (string classname)))))
156
157(defvar *class-name-to-full-case-insensitive* (make-hash-table :test 'equalp))
158
159;; This is the function that calls invoke to call your java
160;; method. The first argument is the method name or 'new. The second
161;; is the object you are calling it on, followed by the rest of the
162;; arguments. If the "object" is a symbol, then that symbol is assumed
163;; to be a java class, and a static method on the class is called,
164;; otherwise a regular method is called.
165
166(defun invoke (method object &rest args)
167  (invoke-restargs method object args))
168
169(defun invoke-restargs (method object args &optional (raw? nil))
170  (let* ((object-as-class-name 
171          (if (symbolp object) (maybe-resolve-class-against-imports object)))
172         (object-as-class 
173          (if object-as-class-name (find-java-class object-as-class-name))))
174    (if (eq method 'new)
175        (apply #'jnew (or object-as-class-name object) args)
176        (if raw?
177            (if (symbolp object)
178                (apply #'jstatic-raw method object-as-class  args)
179                (apply #'jcall-raw method object  args))
180            (if (symbolp object)
181                (apply #'jstatic method object-as-class args)
182                (apply #'jcall method object args))))))
183
184(defconstant +set-accessible+ 
185  (jmethod "java.lang.reflect.AccessibleObject" "setAccessible" "boolean"))
186
187(defun invoke-find-method (method object args)
188  (let ((result 
189         (if (symbolp object)
190                ;;; static method
191             (apply #'jmethod (lookup-class-name object) 
192                    method (mapcar #'jobject-class args))
193                  ;;; instance method
194             (apply #'jresolve-method 
195                    method object args))))
196    (jcall +set-accessible+ result +true+)
197    result))
198
199;; This is the reader macro for java methods. it translates the method
200;; into a lambda form that calls invoke. Which is nice because you
201;; can, e.g. do this: (mapcar #"toString" list-of-java-objects). The reader
202;; macro takes one arg. If 0, then jstatic-raw is called, so that abcl doesn't
203;; automagically convert the returned java object into a lisp object. So
204;; #0"toString" returns a java.lang.String object, where as #"toString" returns
205;; a regular Lisp string as ABCL converts the Java string to a Lisp string.
206
207(eval-when (:compile-toplevel :load-toplevel :execute)
208  (defun read-invoke (stream char arg) 
209    (if (eql arg 1)
210        (progn (asdf:make 'javaparser)
211               (read-sharp-java-expression stream))
212        (progn
213          (unread-char char stream)
214          (let ((name (read stream)))
215            (if (or (find #\. name) (find #\{ name))
216                (jss-transform-to-field name arg)
217                (let ((object-var (gensym))
218                      (args-var (gensym)))
219                  `(lambda (,object-var &rest ,args-var) 
220                     (invoke-restargs ,name  ,object-var ,args-var ,(eql arg 0)))))))))
221  (set-dispatch-macro-character #\# #\" 'read-invoke))
222
223(defmacro with-constant-signature (fname-jname-pairs &body body)
224  "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.
225
226FNAME-JNAME-PAIRS is a list of (symbol function &optional raw)
227elements where symbol will be the symbol bound to the method named by
228the string function.  If the optional parameter raw is non-nil, the
229result will be the raw JVM object, uncoerced by the usual conventions.
230
231Use this macro if you are making a lot of calls and
232want to avoid the overhead of the dynamic dispatch."
233
234  (if (null fname-jname-pairs)
235      `(progn ,@body)
236      (destructuring-bind ((fname jname &optional raw) &rest ignore) fname-jname-pairs
237        (declare (ignore ignore))
238        (let ((varname (gensym)))
239          `(let ((,varname nil))
240             (macrolet ((,fname (&rest args)
241                          `(if ,',varname
242                               (if ,',raw
243                                   (jcall-raw ,',varname ,@args)
244                                   (jcall ,',varname ,@args))
245                               (progn
246                                 (setq ,',varname (invoke-find-method ,',jname ,(car args) (list ,@(rest args))))
247                                 (if ,',raw
248                                     (jcall-raw ,',varname ,@args)
249                                     (jcall ,',varname ,@args))))))
250               (with-constant-signature ,(cdr fname-jname-pairs)
251                 ,@body)))))))
252
253(defvar *class-lookup-overrides*)
254
255(defmacro with-class-lookup-disambiguated (overrides &body body)
256  "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."
257  `(let ((*class-lookup-overrides* ',overrides))
258     ,@body))
259
260(defun maybe-found-in-overridden (name)
261  (when (boundp '*class-lookup-overrides*)
262      (let ((found (find-if (lambda(el) (#"matches" (string el) (concatenate 'string "(?i).*" (string name) "$")))
263                            *class-lookup-overrides*)))
264        (if found
265            (let ((*class-lookup-overrides* nil))
266              (lookup-class-name found))))))
267
268
269(defun lookup-class-name (name &key
270                                 (table *class-name-to-full-case-insensitive*)
271                                 (muffle-warning *muffle-warnings*)
272                                 (return-ambiguous nil))
273  (let ((overridden (maybe-found-in-overridden name)))
274    (when overridden (return-from lookup-class-name overridden)))
275  (setq name (string name))
276  (let* (;; cant (last-name-pattern (#"compile" '|java.util.regex.Pattern| ".*?([^.]*)$"))
277         ;; reason: bootstrap - the class name would have to be looked up...
278         (last-name-pattern (load-time-value (jstatic (jmethod "java.util.regex.Pattern" "compile"
279                                                               (jclass "java.lang.String"))
280                                                      (jclass "java.util.regex.Pattern") 
281                                                      ".*?([^.]*)$")))
282         (last-name 
283          (let ((matcher (#0"matcher" last-name-pattern name)))
284            (#"matches" matcher)
285            (#"group" matcher 1))))
286    (let* ((bucket (gethash last-name *class-name-to-full-case-insensitive*))
287           (bucket-length (length bucket)))
288      (or (find name bucket :test 'equalp)
289          (flet ((matches-end (end full test)
290                   (= (+ (or (search end full :from-end t :test test) -10)
291                         (length end))
292                      (length full)))
293                 (ambiguous (choices)
294                   (if return-ambiguous 
295                       (return-from lookup-class-name choices)
296                       (error "Ambiguous class name: ~a can be ~{~a~^, ~}" name choices))))
297            (if (zerop bucket-length)
298                (progn (unless muffle-warning (warn "can't find class named ~a" name)) nil)
299                (let ((matches (loop for el in bucket when (matches-end name el 'char=) collect el)))
300                  (if (= (length matches) 1)
301                      (car matches)
302                      (if (= (length matches) 0)
303                          (let ((matches (loop for el in bucket when (matches-end name el 'char-equal) collect el)))
304                            (if (= (length matches) 1)
305                                (car matches)
306                                (if (= (length matches) 0)
307                                    (progn (unless muffle-warning (warn "can't find class named ~a" name)) nil)
308                                    (ambiguous matches))))
309                          (ambiguous matches))))))))))
310
311#+(or)
312(defun get-all-jar-classnames (jar-file-name)
313  (let* ((jar (jnew (jconstructor "java.util.jar.JarFile" (jclass "java.lang.String")) (namestring (truename jar-file-name))))
314         (entries (#"entries" jar)))
315    (with-constant-signature ((matcher "matcher" t) (substring "substring")
316                              (jreplace "replace" t) (jlength "length")
317                              (matches "matches") (getname "getName" t)
318                              (next "nextElement" t) (hasmore "hasMoreElements")
319                              (group "group"))
320      (loop while (hasmore entries)
321         for name =  (getname (next entries))
322         with class-pattern = (jstatic "compile" "java.util.regex.Pattern" ".*\\.class$")
323         with name-pattern = (jstatic "compile" "java.util.regex.Pattern" ".*?([^.]*)$")
324         when (matches (matcher class-pattern name))
325         collect
326           (let* ((fullname (substring (jreplace name #\/ #\.) 0 (- (jlength name) 6)))
327                  (matcher (matcher name-pattern fullname))
328                  (name (progn (matches matcher) (group matcher 1))))
329             (cons name fullname))
330            ))))
331#|
332 Under openjdk11 this is around 10x slower than
333
334(list (time (jss::get-all-jar-classnames "/Users/evenson/work/abcl-jdk11/dist/abcl.jar"))
335      (time (jss::%get-all-jar-classnames "/Users/evenson/work/abcl-jdk11/dist/abcl.jar")))
336
3370.034 seconds real time
3382268 cons cells
3390.12 seconds real time
340209164 cons cells
341|#
342(defun get-all-jar-classnames (jar-pathname-or-string)
343  (let* ((jar
344           (if (ext:pathname-jar-p jar-pathname-or-string)
345               jar-pathname-or-string
346               ;; better be a string
347               (ext:as-jar-pathname-archive jar-pathname-or-string)))
348         (entries
349           (directory (merge-pathnames "**/*" jar))))
350    (loop :for entry :in entries
351          :for name = (pathname-name entry)
352          :for type = (pathname-type entry)
353          :when (equal type "class")
354            :collect 
355            (cons
356             name
357             ;;; Fully qualified classname be like 'org.armedbear.lisp.ArgumentListProcessor$ArgumentMatcher'
358             (format nil "~{~a.~}~a" (rest (pathname-directory entry)) name)))))
359
360(defun jar-import (file)
361  "Import all the Java classes contained in the pathname FILE into the JSS dynamic lookup cache."
362  (when (probe-file file)
363    (loop for (name . full-class-name) in (get-all-jar-classnames file)
364       do 
365         (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
366                  :test 'equal))))
367
368(defun new (class-name &rest args)
369  "Invoke the Java constructor for CLASS-NAME with ARGS.
370
371CLASS-NAME may either be a symbol or a string according to the usual JSS conventions."
372  (invoke-restargs 'new class-name args))
373
374(defvar *running-in-osgi* (ignore-errors (jclass "org.osgi.framework.BundleActivator")))
375
376(define-condition no-such-java-field (error)
377  ((field-name
378    :initarg :field-name
379    :reader field-name)
380   (object
381    :initarg :object
382    :reader object))
383  (:report (lambda (c stream)
384             (format stream "Unable to find a field named ~a in ~a"
385                     (field-name c) (object c)))))
386
387(defun get-java-field (object field &optional (try-harder *running-in-osgi*))
388  "Returns the value of the FIELD contained in OBJECT.
389
390If OBJECT is a symbol, it names a (possibly) dot qualified class
391containing a static FIELD.
392
393If OBJECT is an Java object instance, the corresponding FIELD instance
394is returned.
395
396If TRY-HARDER is non-nil, returns fields of classes regardless of Java
397private/protected access modifiers."
398  (if try-harder
399      (let* ((class (if (symbolp object)
400                        (setq object (find-java-class object))
401                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)))
402                            object
403                            (jobject-class object))))
404             (jfield (if (java-object-p field)
405                         field
406                         (or (find-declared-field field class)
407                             (error 'no-such-java-field :field-name field :object object)))))
408        (#"setAccessible" jfield +true+)
409        (values (#"get" jfield object) jfield))
410      (if (symbolp object)
411          (let ((class (find-java-class object)))
412            (jfield class field))
413          (jfield field object))))
414
415(defun find-declared-field (field class)
416  "Return a FIELD object corresponding to the definition of FIELD \(a
417string\) visible at CLASS. *Not* restricted to public fields or
418classes, and checks all superclasses of CLASS.
419
420Returns NIL if no field object is found."
421  (loop while class
422     for field-obj = (get-declared-field class field)
423     if field-obj
424     do (return-from find-declared-field field-obj)
425     else
426     do (setf class (jclass-superclass class)))
427  nil)
428
429(defun get-declared-field (class fieldname)
430  (find fieldname (#"getDeclaredFields" class)
431        :key 'jfield-name :test 'equal))
432
433;; TODO use #"getSuperclass" and #"getInterfaces" to see whether there
434;; are fields in superclasses that we might set
435(defun set-java-field (object field value &optional (try-harder *running-in-osgi*))
436  "Set the FIELD of OBJECT to VALUE.
437If OBJECT is a symbol, it names a dot qualified Java class to look for
438a static FIELD.  If OBJECT is an instance of java:java-object, the
439associated is used to look up the static FIELD."
440  (if try-harder
441      (let* ((class (if (symbolp object)
442                        (setq object (find-java-class object))
443                        (if (equal "java.lang.Class" (jclass-name (jobject-class object)) )
444                            object
445                            (jobject-class object))))
446             (jfield (if (java-object-p field)
447                         field
448                         (or (find-declared-field field class)
449                             (error 'no-such-java-field :field-name field :object object)))))
450        (#"setAccessible" jfield +true+)
451        (values (#"set" jfield object value) jfield))
452      (if (symbolp object)
453          (let ((class (find-java-class object)))
454            (setf (jfield (#"getName" class) field) value))
455          (if (typep object 'java-object)
456              (setf (jfield (jclass-of object) field) value)
457              (setf (jfield object field) value)))))
458
459(defun (setf get-java-field) (value object field &optional (try-harder *running-in-osgi*))
460  (set-java-field object field value try-harder))
461
462(defconstant +for-name+ 
463  (jmethod "java.lang.Class" "forName" "java.lang.String" "boolean" "java.lang.ClassLoader"))
464
465(defun find-java-class (name)
466  (or (jstatic +for-name+ "java.lang.Class" 
467               (maybe-resolve-class-against-imports name) +true+ java::*classloader*)
468      (ignore-errors (jclass (maybe-resolve-class-against-imports name)))))
469
470(defmethod print-object ((obj (jclass "java.lang.Class")) stream) 
471  (print-unreadable-object (obj stream :identity nil)
472    (format stream "java class ~a" (jclass-name obj))))
473
474(defmethod print-object ((obj (jclass "java.lang.reflect.Method")) stream) 
475  (print-unreadable-object (obj stream :identity nil)
476    (format stream "method ~a" (#"toString" obj))))
477
478(defun do-auto-imports ()
479  (if (sys::system-artifacts-are-jars-p)
480      (do-auto-imports-from-jars)
481      (progn
482        ;;; First, import all the classes available from the module system
483        (do-auto-imports-from-modules)
484        ;;; Then, introspect any jars that appear on the classpath
485        (loop :for entry :in (second (multiple-value-list (sys::java.class.path)))
486             :doing (let ((p (pathname entry)))
487                      (when (string-equal (pathname-type p) "jar")
488                        (jar-import p)))))))
489
490(defun do-auto-imports-from-modules ()
491  (loop :for (name . full-class-name) :in (all-class-names-from-modules)
492     :doing
493       (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
494                :test 'equal)))
495
496(defun all-class-names-from-modules ()
497  (let ((class-pattern (jstatic "compile" "java.util.regex.Pattern" ".*\\.class$"))
498        (name-pattern (jstatic "compile" "java.util.regex.Pattern" ".*?([^.]*)$")))
499    (loop
500       :for module :across (chain (jstatic "boot" "java.lang.ModuleLayer")
501                                  "configuration" "modules" "stream" "toArray")
502       :appending
503         (loop
504            :for class-as-path :across (chain module "reference" "open" "list" "toArray")
505            :when
506              (jcall "matches" (jcall "matcher" class-pattern class-as-path))
507            :collect
508              (let* ((full-name (jcall "substring" (jcall "replace" class-as-path #\/ #\.)
509                                           0
510                                           (- (jcall "length" class-as-path) (jcall "length" ".class"))))
511                     (matcher (jcall "matcher" name-pattern full-name))
512                     (name (progn
513                             (jcall "matches" matcher)
514                             (jcall "group" matcher 1))))
515                (cons name full-name))))))
516
517(defun do-auto-imports-from-jars ()
518  (labels ((expand-paths (cp)
519             (loop :for s :in cp
520                :appending (loop :for entry 
521                              :in (let ((p (pathname s)))
522                                    (if (wild-pathname-p p)
523                                        (directory p)
524                                        (list p)))
525                              :collecting entry)))
526           (import-classpath (cp)
527             (mapcar 
528              (lambda (p) 
529                (when *load-verbose*
530                  (format t ";; Importing ~A~%" p))
531                (cond 
532                  ((file-directory-p p) )
533                  ((equal (pathname-type p) "jar")
534                   (jar-import (merge-pathnames p
535                                                (format nil "~a/" (jstatic "getProperty" "java.lang.System" "user.dir")))))))
536              cp))
537           (split-classpath (cp)
538             (coerce 
539              (jcall "split" cp 
540                     (string (jfield (jclass "java.io.File") "pathSeparatorChar")))
541              'cons))
542           (do-imports (cp)
543             (import-classpath (expand-paths (split-classpath cp)))))
544
545    (let ((mx-bean (jstatic "getRuntimeMXBean"
546                            '|java.lang.management.ManagementFactory|)))
547      (do-imports (jcall "getClassPath" mx-bean))
548      (do-imports (jcall "getBootClassPath" mx-bean)))))
549
550(eval-when (:load-toplevel :execute)
551  (when *do-auto-imports* 
552    (do-auto-imports)))
553
554(defun japropos (string)
555  "Output the names of all Java class names loaded in the current process which match STRING.."
556  (setq string (string string))
557  (let ((matches nil))
558    (maphash (lambda(key value) 
559               (declare (ignore key))
560               (loop for class in value
561                  when (search string class :test 'string-equal)
562                  do (pushnew (list class "Java Class") matches :test 'equal)))
563             *class-name-to-full-case-insensitive*)
564    (loop for (match type) in (sort matches 'string-lessp :key 'car)
565       do (format t "~a: ~a~%" match type))
566    ))
567
568(defun jclass-method-names (class &optional full)
569  (if (java-object-p class)
570      (if (equal (jclass-name (jobject-class class)) "java.lang.Class")
571          (setq class (jclass-name class))
572          (setq class (jclass-name (jobject-class class)))))
573  (union
574   (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getMethods" (find-java-class class))) :test 'equal)
575   (ignore-errors (remove-duplicates (map 'list (if full #"toString" 'jmethod-name) (#"getConstructors" (find-java-class class))) :test 'equal))))
576
577(defun java-class-method-names (class &optional stream)
578  "Return a list of the public methods encapsulated by the JVM CLASS.
579
580If STREAM non-nil, output a verbose description to the named output stream.
581
582CLASS may either be a string naming a fully qualified JVM class in dot
583notation, or a symbol resolved against all class entries in the
584current classpath."
585  (if stream
586      (dolist (method (jclass-method-names class t))
587        (format stream "~a~%" method))
588      (jclass-method-names class)))
589
590(setf (symbol-function 'jcmn) #'java-class-method-names)
591
592(defun path-to-class (classname)
593  (let ((full (lookup-class-name classname)))
594    (#"toString" 
595     (#"getResource" 
596      (find-java-class full)
597      (concatenate 'string "/" (substitute #\/ #\. full) ".class")))))
598
599;; http://www.javaworld.com/javaworld/javaqa/2003-07/02-qa-0725-classsrc2.html
600
601(defun all-loaded-classes ()
602  (let ((classes-field 
603         (find "classes" (#"getDeclaredFields" (jclass "java.lang.ClassLoader"))
604               :key #"getName" :test 'equal)))
605    (#"setAccessible" classes-field +true+)
606    (loop for classloader in (mapcar #'first (dump-classpath))
607       append
608         (loop with classesv = (#"get" classes-field classloader)
609            for i below (#"size" classesv)
610            collect (#"getName" (#"elementAt" classesv i)))
611       append
612         (loop with classesv = (#"get" classes-field (#"getParent" classloader))
613            for i below (#"size" classesv)
614            collect (#"getName" (#"elementAt" classesv i))))))
615
616(defun get-dynamic-class-path ()
617  (rest 
618   (find-if (lambda (loader) 
619              (string= "org.armedbear.lisp.JavaClassLoader"
620                       (jclass-name (jobject-class loader))))
621            (dump-classpath)
622            :key #'car)))
623
624(defun java-gc ()
625  (#"gc" (#"getRuntime" 'java.lang.runtime))
626  (#"runFinalization" (#"getRuntime" 'java.lang.runtime))
627  (#"gc" (#"getRuntime" 'java.lang.runtime))
628  (java-room))
629
630(defun java-room ()
631  (let ((rt (#"getRuntime" 'java.lang.runtime)))
632    (values (- (#"totalMemory" rt) (#"freeMemory" rt))
633            (#"totalMemory" rt)
634            (#"freeMemory" rt)
635            (list :used :total :free))))
636
637(defun verbose-gc (&optional (new-value nil new-value-supplied))
638  (if new-value-supplied
639      (progn (#"setVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory) new-value) new-value)
640      (#"isVerbose" (#"getMemoryMXBean"  'java.lang.management.ManagementFactory))))
641
642(defun all-jars-below (directory) 
643  (loop with q = (system:list-directory directory) 
644     while q for top = (pop q)
645     if (null (pathname-name top)) do (setq q (append q (all-jars-below top))) 
646     if (equal (pathname-type top) "jar") collect top))
647
648(defun all-classfiles-below (directory) 
649  (loop with q = (system:list-directory directory) 
650     while q for top = (pop q)
651     if (null (pathname-name top)) do (setq q (append q (all-classfiles-below top ))) 
652     if (equal (pathname-type top) "class")
653     collect top
654       ))
655
656(defun all-classes-below-directory (directory)
657  (loop for file in (all-classfiles-below directory) collect
658       (format nil "~{~a.~}~a"
659               (subseq (pathname-directory file) (length (pathname-directory directory)))
660               (pathname-name file))
661       ))
662
663(defun classfiles-import (directory)
664  "Load all Java classes recursively contained under DIRECTORY in the current process."
665  (setq directory (truename directory))
666  (loop for full-class-name in (all-classes-below-directory directory)
667     for name = (#"replaceAll" full-class-name "^.*\\." "")
668     do
669       (pushnew full-class-name (gethash name *class-name-to-full-case-insensitive*) 
670                :test 'equal)))
671
672(defun jclass-all-interfaces (class)
673  "Return a list of interfaces the class implements"
674  (unless (java-object-p class)
675    (setq class (find-java-class class)))
676  (loop for aclass = class then (#"getSuperclass" aclass)
677     while aclass
678     append (coerce (#"getInterfaces" aclass) 'list)))
679
680(defun safely (f name)
681  (let ((fname (gensym)))
682    (compile fname
683             `(lambda(&rest args)
684                (with-simple-restart (top-level
685                                      "Return from lisp method implementation for ~a." ,name)
686                  (apply ,f args))))
687    (symbol-function fname)))
688
689(defun jdelegating-interface-implementation (interface dispatch-to &rest method-names-and-defs)
690  "Creates and returns an implementation of a Java interface with
691   methods calling Lisp closures as given in METHOD-NAMES-AND-DEFS.
692
693   INTERFACE is an interface
694
695   DISPATCH-TO is an existing Java object
696
697   METHOD-NAMES-AND-DEFS is an alternating list of method names
698   (strings) and method definitions (closures).
699
700   For missing methods, a dummy implementation is provided that
701   calls the method on DISPATCH-TO."
702  (let ((implemented-methods
703         (loop for m in method-names-and-defs
704            for i from 0
705            if (evenp i) 
706            do (assert (stringp m) (m) "Method names must be strings: ~s" m) and collect m
707            else
708            do (assert (or (symbolp m) (functionp m)) (m) "Methods must be function designators: ~s" m))))
709    (let ((safe-method-names-and-defs 
710           (loop for (name function) on method-names-and-defs by #'cddr
711              collect name collect (safely function name))))
712      (loop for method across
713           (jclass-methods interface :declared nil :public t)
714         for method-name = (jmethod-name method)
715         when (not (member method-name implemented-methods :test #'string=))
716         do
717           (let* ((def  `(lambda
718                             (&rest args)
719                           (invoke-restargs ,(jmethod-name method) ,dispatch-to args t)
720                           )))
721             (push (coerce def 'function) safe-method-names-and-defs)
722             (push method-name safe-method-names-and-defs)))
723      (apply #'java::%jnew-proxy  interface safe-method-names-and-defs))))
724
725
Note: See TracBrowser for help on using the repository browser.