| 1 | (in-package :cl-user) |
|---|
| 2 | ;;;; Copyright (C) 2010 by Mark Evenson |
|---|
| 3 | |
|---|
| 4 | #| |
|---|
| 5 | |
|---|
| 6 | A tour of the ABCL Java FFI by defining a Java interface at return, |
|---|
| 7 | creating a Java proxy implementation that provides a Lisp |
|---|
| 8 | implementation, and then use of the Java Reflection API to actually |
|---|
| 9 | invoke the Lisp implementation. |
|---|
| 10 | |
|---|
| 11 | This 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 | |
|---|
| 19 | METHODS is a list of (NAME RETURN-TYPE (ARG-TYPES)) entries. NAME is |
|---|
| 20 | a string. The values of RETURN-TYPE and the list of ARG-TYPES for the |
|---|
| 21 | defined method follow the are either references to Java objects as |
|---|
| 22 | created by JVM::MAKE-JVM-CLASS-NAME, or keywords representing Java |
|---|
| 23 | primtive types as contained in JVM::MAP-PRIMITIVE-TYPE. |
|---|
| 24 | |
|---|
| 25 | SUPERINTERFACES optionally contains a list of interfaces that this |
|---|
| 26 | interface 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 | |
|---|