source: trunk/abcl/examples/misc/dynamic-interfaces.lisp

Last change on this file was 13089, checked in by Mark Evenson, 13 years ago

An example of using the ability to dynamically create Java interfaces.

This can probably be cleaned up a lot. Among other things, it shows a
nearly constant need to protect the "raw" Java values from ABCL's
interpretation to do anything useful. For example

(let ((c (jclass "java.io.File")))

(jnew-array-from-array "java.lang.Class" #(c c c)))

fails to construct an array as the java.lang.Class members are
promoted to JAVA-OBJECT. Does this mean we need
JNEW-ARRAY-FROM-ARRAY-RAW? Or do we need to try both interpretations?

File size: 5.6 KB
Line 
1(in-package :cl-user)
2;;;; Copyright (C) 2010 by Mark Evenson
3
4#|
5
6A tour of the ABCL Java FFI by defining a Java interface at return,
7creating a Java proxy implementation that provides a Lisp
8implementation, and then use of the Java Reflection API to actually
9invoke the Lisp implementation.
10
11This needs abcl-0.24.0-dev or later.
12
13|#
14
15(defun define-java-interface (name package methods 
16                              &optional (superinterfaces nil))
17"Define a class for a Java interface called NAME in PACKAGE with METHODS.
18
19METHODS is a list of (NAME RETURN-TYPE (ARG-TYPES)) entries.  NAME is
20a string.  The values of RETURN-TYPE and the list of ARG-TYPES for the
21defined method follow the are either references to Java objects as
22created by JVM::MAKE-JVM-CLASS-NAME, or keywords representing Java
23primtive types as contained in JVM::MAP-PRIMITIVE-TYPE.
24
25SUPERINTERFACES optionally contains a list of interfaces that this
26interface extends specified as fully qualifed dotted Java names."
27  (let* ((class-name-string (format nil "~A/~A" package name))
28         (class-name (jvm::make-jvm-class-name class-name-string))
29         (class (jvm::make-class-interface-file class-name)))
30    (dolist (superinterface superinterfaces)
31      (jvm::class-add-superinterface 
32       class 
33       (if (type-of superinterface 'jvm::jvm-class-name)
34           superinterface
35           (jvm::make-jvm-class-name superinterface))))
36    (dolist (method methods)
37      (let ((name (first method))
38            (returns (second method))
39            (args (third method)))
40      (jvm::class-add-method
41       class
42       (jvm::make-jvm-method name returns args
43                             :flags '(:public :abstract)))))
44    (jvm::finalize-class-file class)
45    (let ((s (sys::%make-byte-array-output-stream)))
46      (jvm::write-class-file class s)
47      (sys::%get-output-stream-bytes s))))
48   
49(defun load-class (class-bytes) 
50  "Load the Java byte[] array CLASS-BYTES as a Java class."
51  (let ((load-class-method 
52         (jmethod "org.armedbear.lisp.JavaClassLoader"
53                  "loadClassFromByteArray" "[B")))
54    (jcall load-class-method java::*classloader* class-bytes)))
55
56;;; Unused in the interface example, but useful to get at the class
57;;; definition with javap or jad
58(defun write-class (class-bytes pathname)
59  "Write the Java byte[] array CLASS-BYTES to PATHNAME."
60  (with-open-file (stream pathname 
61                          :direction :output 
62                          :element-type '(signed-byte 8))
63    (dotimes (i (jarray-length class-bytes))
64      (write-byte (jarray-ref class-bytes i) stream))))
65
66;;;; The example begins here.  We store all the intermediate values as
67;;;; parameters so they may be inspected by those that follow this example.
68
69;;; Construct a Java interface as an array of bytes containing the
70;;; Java class
71;;;
72;;; This corresponds to the Java source:
73;;;
74;;;   package org.not.tmp;
75;;;   public interface Foo {
76;;;     public int add(int a, int b);
77;;;   }
78(defparameter *foo-bytes*
79  (define-java-interface "Foo" "org/not/tmp" 
80    '(("add" :int (:int :int)))))
81
82;;; Load the class definition into the JVM
83(defparameter *foo-interface-class* (load-class *foo-bytes*))
84
85;;; Create an implementation of the interface in Lisp.
86(defparameter *foo*
87  (jinterface-implementation "org.not.tmp.Foo"
88                             "add" 
89                             (lambda (a b) 
90                               (reduce  #'+
91                                        (mapcar (lambda (n) 
92                                                  (jcall "intValue" n))
93                                                (list a b))))))
94
95;;; To get the class of what we just defined, we have to use Proxy.getProxyClass()
96(defparameter *foo-class*
97       ;; XXX would prettier if something like
98       ;;   (jarray-from-array-raw `#(,*foo-class*))
99       ;; existed.
100       (let ((interface-array (jnew-array "java.lang.Class" 1)))
101         (setf (jarray-ref interface-array 0) *foo-interface-class*)
102         (jstatic-raw "getProxyClass" "java.lang.reflect.Proxy" 
103                      java::*classloader* interface-array)))
104         
105
106;;; Get a reference to the callable instance of this method.
107(defparameter *callable-foo* 
108  (jstatic-raw "getInvocationHandler" "java.lang.reflect.Proxy"  *foo*))
109
110;;; In order to use *callable-foo* we need to reflect the method we are
111;;; going to invoke.
112
113;;; First we construct a Java array of classes for the parameters
114(defparameter *add-parameters*
115  ;; XXX again a jnew-array-from-array-raw would help here.
116  (let ((parameters (jnew-array "java.lang.Class" 2)))
117    (setf (jarray-ref parameters 0)
118          (jfield-raw "java.lang.Integer" "TYPE")
119          (jarray-ref parameters 1)
120          (jfield-raw "java.lang.Integer" "TYPE"))
121    parameters))
122
123;;; Then we get the reflected instance of the method.
124(defparameter *add-method*
125  (jcall "getMethod" *foo-class* "add" *add-parameters*))
126
127;;; Now we construct the actual arguments we are going to call with
128(defparameter *add-args*
129  (let ((args (jnew-array "java.lang.Integer" 2)))
130    (setf (jarray-ref args 0)
131          (jnew "java.lang.Integer" 2)
132          (jarray-ref args 1)
133          (jnew "java.lang.Integer" 2))
134    args))
135
136;;; It isn't strictly necessary to define the method parameter to
137;;; JCALL in this manner, but it is more efficient in that the runtime
138;;; does not have to dynamically introspect for the correct method. 
139(defconstant +invocation-handler-invoke+
140  (jmethod "java.lang.reflect.InvocationHandler" 
141           "invoke" "java.lang.Object" "java.lang.reflect.Method" "[Ljava.lang.Object;"))
142
143;; And finally we can make the call
144#|
145(jcall +invocation-handler-invoke+ *callable-foo* *foo* *add-method* *add-args*)
146|#
147
Note: See TracBrowser for help on using the repository browser.