1 | (require "JVM") |
---|
2 | |
---|
3 | ;;The package is set to :jvm for convenience, since most of the symbols used |
---|
4 | ;;here come from that package. However, the functions we're definining belong |
---|
5 | ;;to the :java package. |
---|
6 | (in-package :jvm) |
---|
7 | |
---|
8 | (defconstant +abcl-java-object+ (make-jvm-class-name "org.armedbear.lisp.JavaObject")) |
---|
9 | |
---|
10 | (defun java:jnew-runtime-class |
---|
11 | (class-name &rest args &key (superclass "java.lang.Object") |
---|
12 | interfaces constructors methods fields (access-flags '(:public)) annotations) |
---|
13 | "Creates and loads a Java class with methods calling Lisp closures |
---|
14 | as given in METHODS. CLASS-NAME and SUPER-NAME are strings, |
---|
15 | INTERFACES is a list of strings, CONSTRUCTORS, METHODS and FIELDS are |
---|
16 | lists of constructor, method and field definitions. |
---|
17 | |
---|
18 | Constructor definitions - currently NOT supported - are lists of the form |
---|
19 | (argument-types function &optional super-invocation-arguments) |
---|
20 | where argument-types is a list of strings and function is a lisp function of |
---|
21 | (1+ (length argument-types)) arguments; the instance (`this') is passed in as |
---|
22 | the last argument. The optional super-invocation-arguments is a list of numbers |
---|
23 | between 1 and (length argument-types), where the number k stands for the kth argument |
---|
24 | to the just defined constructor. If present, the constructor of the superclass |
---|
25 | will be called with the appropriate arguments. E.g., if the constructor definition is |
---|
26 | ((\"java.lang.String\" \"int\") #'(lambda (string i this) ...) (2 1)) |
---|
27 | then the constructor of the superclass with argument types (int, java.lang.String) will |
---|
28 | be called with the second and first arguments. |
---|
29 | |
---|
30 | Method definitions are lists of the form |
---|
31 | (method-name return-type argument-types function &key modifiers annotations) |
---|
32 | where method-name is a string, return-type and argument-types are strings or keywords for |
---|
33 | primitive types (:void, :int, etc.), and function is a Lisp function of minimum arity |
---|
34 | (1+ (length argument-types)); the instance (`this') is passed in as the first argument. |
---|
35 | |
---|
36 | Field definitions are lists of the form (field-name type &key modifiers annotations)." |
---|
37 | (declare (ignorable superclass interfaces constructors methods fields access-flags annotations)) |
---|
38 | (let* ((stream (sys::%make-byte-array-output-stream)) |
---|
39 | (current-class-loader (java:get-current-classloader)) |
---|
40 | (memory-class-loader (java:jnew "org.armedbear.lisp.MemoryClassLoader" current-class-loader))) |
---|
41 | (multiple-value-bind (class-file method-implementation-fields) |
---|
42 | (apply #'java::%jnew-runtime-class class-name stream args) |
---|
43 | (sys::put-memory-function memory-class-loader |
---|
44 | class-name (sys::%get-output-stream-bytes stream)) |
---|
45 | (let ((jclass (java:jcall "loadClass" memory-class-loader class-name))) |
---|
46 | (dolist (method method-implementation-fields) |
---|
47 | (setf (java:jfield jclass (car method)) (cdr method))) |
---|
48 | jclass)))) |
---|
49 | |
---|
50 | (defun java::%jnew-runtime-class |
---|
51 | (class-name stream &key (superclass "java.lang.Object") |
---|
52 | interfaces constructors methods fields (access-flags '(:public)) annotations) |
---|
53 | "Actual implementation of jnew-runtime-class. Writes the class bytes to a stream. Returns two values: the finalized class-file structure and the alist of method implementation fields." |
---|
54 | (let* ((jvm-class-name (make-jvm-class-name class-name)) |
---|
55 | (class-file (make-class-file jvm-class-name (make-jvm-class-name superclass) access-flags)) |
---|
56 | method-implementation-fields) |
---|
57 | (setf (class-file-interfaces class-file) |
---|
58 | (mapcar #'make-jvm-class-name interfaces)) |
---|
59 | (when annotations |
---|
60 | (class-add-attribute class-file (make-runtime-visible-annotations-attribute |
---|
61 | :list (mapcar #'parse-annotation annotations)))) |
---|
62 | (setf method-implementation-fields (java::runtime-class-add-methods class-file methods)) |
---|
63 | (java::runtime-class-add-fields class-file fields) |
---|
64 | (if (null constructors) |
---|
65 | (let ((ctor (make-jvm-method :constructor :void nil :flags '(:public)))) |
---|
66 | (class-add-method class-file ctor) |
---|
67 | (with-code-to-method (class-file ctor) |
---|
68 | (aload 0) |
---|
69 | (emit-invokespecial-init (class-file-superclass class-file) nil) |
---|
70 | (emit 'return))) |
---|
71 | (error "constructors not supported")) |
---|
72 | (finalize-class-file class-file) |
---|
73 | (write-class-file class-file stream) |
---|
74 | (finish-output stream) |
---|
75 | #+test-record-generated-class-file |
---|
76 | (with-open-file (f (format nil "~A.class" class-name) :direction :output :element-type '(signed-byte 8)) |
---|
77 | (write-sequence (java::list-from-jarray (sys::%get-output-stream-bytes stream)) f)) |
---|
78 | (values class-file method-implementation-fields))) |
---|
79 | |
---|
80 | (defun java::make-accessor-name (prefix name) |
---|
81 | (let ((initial (char-upcase (aref name 0))) |
---|
82 | (rest (subseq name 1))) |
---|
83 | (format nil "~A~A~A" prefix initial rest))) |
---|
84 | |
---|
85 | ;;This is missing from compiler-pass2.lisp. Probably this and similar functions should reside |
---|
86 | ;;in a dedicated file, independent from both runtime-class and compiler-pass2. |
---|
87 | (defun emit-invokespecial (class-name method-name arg-types return-type) |
---|
88 | (let* ((stack-effect (apply #'descriptor-stack-effect return-type arg-types)) |
---|
89 | (index (pool-add-method-ref *pool* class-name |
---|
90 | method-name (cons return-type arg-types))) |
---|
91 | (instruction (apply #'%emit 'invokespecial (u2 index)))) |
---|
92 | (declare (type (signed-byte 8) stack-effect)) |
---|
93 | (setf (instruction-stack instruction) (1- stack-effect)))) |
---|
94 | |
---|
95 | (defun java::canonicalize-java-type (type) |
---|
96 | (cond |
---|
97 | ((stringp type) (make-jvm-class-name type)) |
---|
98 | ((keywordp type) type) |
---|
99 | (t (error "Unrecognized Java type: ~A" type)))) |
---|
100 | |
---|
101 | (defun java::emit-unbox-and-return (return-type) |
---|
102 | (cond |
---|
103 | ((eq return-type :void) |
---|
104 | (emit 'pop) |
---|
105 | (emit 'return)) |
---|
106 | ((eq return-type :int) |
---|
107 | (emit-invokevirtual +lisp-object+ "intValue" nil :int) |
---|
108 | (emit 'ireturn)) |
---|
109 | ((eq return-type :boolean) |
---|
110 | (emit-invokevirtual +lisp-object+ "getBooleanValue" nil :boolean) |
---|
111 | (emit 'ireturn)) |
---|
112 | ((jvm-class-name-p return-type) |
---|
113 | (emit-invokevirtual +lisp-object+ "javaInstance" nil +java-object+) |
---|
114 | (emit-checkcast return-type) |
---|
115 | (emit 'areturn)) |
---|
116 | (t |
---|
117 | (error "Unsupported return type: ~A" return-type)))) |
---|
118 | |
---|
119 | (defun java::runtime-class-add-methods (class-file methods) |
---|
120 | (let (method-implementation-fields) |
---|
121 | (dolist (m methods) |
---|
122 | (destructuring-bind (name return-type argument-types function |
---|
123 | &key (modifiers '(:public)) annotations override) m |
---|
124 | (let* ((argument-types (mapcar #'java::canonicalize-java-type argument-types)) |
---|
125 | (argc (length argument-types)) |
---|
126 | (return-type (java::canonicalize-java-type return-type)) |
---|
127 | (jmethod (make-jvm-method name return-type argument-types :flags modifiers)) |
---|
128 | (field-name (string (gensym name)))) |
---|
129 | (class-add-method class-file jmethod) |
---|
130 | (let ((field (make-field field-name +lisp-object+ :flags '(:public :static)))) |
---|
131 | (class-add-field class-file field) |
---|
132 | (push (cons field-name function) method-implementation-fields)) |
---|
133 | (when annotations |
---|
134 | (method-add-attribute jmethod (make-runtime-visible-annotations-attribute |
---|
135 | :list (mapcar #'parse-annotation annotations)))) |
---|
136 | (with-code-to-method (class-file jmethod) |
---|
137 | ;;Allocate registers (2 * argc to load and store arguments + 2 to box "this") |
---|
138 | (dotimes (i (* 2 (1+ argc))) |
---|
139 | (allocate-register nil)) |
---|
140 | ;;Box "this" (to be passed as the first argument to the Lisp function) |
---|
141 | (aload 0) |
---|
142 | (emit 'iconst_1) ;;true |
---|
143 | (emit-invokestatic +abcl-java-object+ "getInstance" |
---|
144 | (list +java-object+ :boolean) +lisp-object+) |
---|
145 | (astore (1+ argc)) |
---|
146 | ;;Box each argument |
---|
147 | (loop |
---|
148 | :for arg-type :in argument-types |
---|
149 | :for i :from 1 |
---|
150 | :do (progn |
---|
151 | (cond |
---|
152 | ((keywordp arg-type) |
---|
153 | (error "Unsupported arg-type: ~A" arg-type)) |
---|
154 | ((eq arg-type :int) :todo) |
---|
155 | (t (aload i) |
---|
156 | (emit 'iconst_1) ;;true |
---|
157 | (emit-invokestatic +abcl-java-object+ "getInstance" |
---|
158 | (list +java-object+ :boolean) +lisp-object+))) |
---|
159 | (astore (+ i (1+ argc))))) |
---|
160 | ;;Load the Lisp function from its static field |
---|
161 | (emit-getstatic (class-file-class class-file) field-name +lisp-object+) |
---|
162 | (if (<= (1+ argc) call-registers-limit) |
---|
163 | (progn |
---|
164 | ;;Load the boxed this |
---|
165 | (aload (1+ argc)) |
---|
166 | ;;Load each boxed argument |
---|
167 | (dotimes (i argc) |
---|
168 | (aload (+ argc 2 i)))) |
---|
169 | (error "execute(LispObject[]) is currently not supported")) |
---|
170 | (emit-call-execute (1+ (length argument-types))) |
---|
171 | (java::emit-unbox-and-return return-type)) |
---|
172 | (cond |
---|
173 | ((eq override t) |
---|
174 | (let ((super-method |
---|
175 | (make-jvm-method (format nil "super$~A" name) |
---|
176 | return-type argument-types :flags modifiers))) |
---|
177 | (class-add-method class-file super-method) |
---|
178 | (with-code-to-method (class-file super-method) |
---|
179 | (dotimes (i (1+ (length argument-types))) |
---|
180 | (allocate-register nil)) |
---|
181 | (aload 0) |
---|
182 | (loop |
---|
183 | :for arg-type :in argument-types |
---|
184 | :for i :from 1 |
---|
185 | :do (progn |
---|
186 | (cond |
---|
187 | ((keywordp arg-type) |
---|
188 | (error "Unsupported arg-type: ~A" arg-type)) |
---|
189 | ((eq arg-type :int) :todo) |
---|
190 | (t (aload i))))) |
---|
191 | (emit-invokespecial (class-file-superclass class-file) name |
---|
192 | argument-types return-type) |
---|
193 | ;(emit 'pop) |
---|
194 | (cond |
---|
195 | ((eq return-type :void) |
---|
196 | (emit 'return)) |
---|
197 | ((eq return-type :int) |
---|
198 | (emit 'ireturn)) |
---|
199 | ((eq return-type :boolean) |
---|
200 | (emit 'ireturn)) |
---|
201 | ((jvm-class-name-p return-type) |
---|
202 | (emit 'areturn)) |
---|
203 | (t |
---|
204 | (error "Unsupported return type: ~A" return-type)))))))))) |
---|
205 | method-implementation-fields)) |
---|
206 | |
---|
207 | (defun java::runtime-class-add-fields (class-file fields) |
---|
208 | (dolist (field-spec fields) |
---|
209 | (destructuring-bind (name type &key (modifiers '(:public)) annotations |
---|
210 | (getter nil getter-p) (setter nil setter-p) |
---|
211 | (property (and (not getter-p) (not setter-p)))) |
---|
212 | field-spec |
---|
213 | (let* ((type (if (keywordp type) type (make-jvm-class-name type))) |
---|
214 | (field (make-field name type :flags modifiers))) |
---|
215 | (when (member :static modifiers) |
---|
216 | (setf property nil getter nil setter nil)) |
---|
217 | (when annotations |
---|
218 | (field-add-attribute field (make-runtime-visible-annotations-attribute |
---|
219 | :list (mapcar #'parse-annotation annotations)))) |
---|
220 | (class-add-field class-file field) |
---|
221 | (when (or getter property) |
---|
222 | (unless (stringp getter) |
---|
223 | (setf getter (java::make-accessor-name "get" (if (stringp property) property name)))) |
---|
224 | (let ((jmethod (make-jvm-method getter type nil :flags '(:public)))) |
---|
225 | (class-add-method class-file jmethod) |
---|
226 | (with-code-to-method (class-file jmethod) |
---|
227 | (aload 0) |
---|
228 | (emit-getfield (class-file-class class-file) name type) |
---|
229 | (cond |
---|
230 | ((jvm-class-name-p type) (emit 'areturn)) |
---|
231 | ((eq type :int) (emit 'ireturn)) |
---|
232 | (t (error "Unsupported getter return type: ~A" type)))))) |
---|
233 | (when (or setter property) |
---|
234 | (unless (stringp setter) |
---|
235 | (setf setter (java::make-accessor-name "set" (if (stringp property) property name)))) |
---|
236 | (let ((jmethod (make-jvm-method setter :void (list type) :flags '(:public)))) |
---|
237 | (class-add-method class-file jmethod) |
---|
238 | (with-code-to-method (class-file jmethod) |
---|
239 | (aload 0) |
---|
240 | (cond |
---|
241 | ((jvm-class-name-p type) (aload 1)) |
---|
242 | ((eq type :int) (emit 'iload 1)) |
---|
243 | (t (error "Unsupported setter parameter type: ~A" type))) |
---|
244 | (emit-putfield (class-file-class class-file) name type) |
---|
245 | (emit 'return)))))))) |
---|
246 | |
---|
247 | (defmacro java:define-java-class () :todo) |
---|
248 | |
---|
249 | (defun parse-annotation (annotation) |
---|
250 | (when (annotation-p annotation) |
---|
251 | (return-from parse-annotation annotation)) |
---|
252 | (destructuring-bind (class &rest elements) (if (listp annotation) annotation (list annotation)) |
---|
253 | (let (actual-elements) |
---|
254 | (dolist (elem elements) |
---|
255 | (push (parse-annotation-element elem) actual-elements)) |
---|
256 | (make-annotation :type class :elements (nreverse actual-elements))))) |
---|
257 | |
---|
258 | (defun parse-annotation-element (elem) |
---|
259 | (cond |
---|
260 | ((annotation-element-p elem) elem) |
---|
261 | ((atom elem) (make-primitive-or-string-annotation-element :name nil :value elem)) |
---|
262 | ((keywordp (car elem)) (parse-annotation-element `("value" ,@elem))) |
---|
263 | (t |
---|
264 | (destructuring-bind (name &key value enum annotation) elem |
---|
265 | (cond |
---|
266 | (enum (make-enum-value-annotation-element :name name :type enum :value value)) |
---|
267 | (annotation |
---|
268 | (make-annotation-value-annotation-element :name name :value (parse-annotation annotation))) |
---|
269 | ((listp value) |
---|
270 | (make-array-annotation-element :name name :values (mapcar #'parse-annotation-element value))) |
---|
271 | (t (make-primitive-or-string-annotation-element :name name :value value))))))) |
---|
272 | |
---|
273 | ;;TODO: |
---|
274 | ;; - Returning nil as null is broken |
---|
275 | ;; - Function calls with 8+ args |
---|
276 | ;; - super method invocation. Idea: generate companion methods super_... to use with plain jcall. Add a flag per method to optionally disable this when not needed. |
---|
277 | ;; - Constructors |
---|
278 | ;; - optional accessors (CLOS methods) for properties? |
---|
279 | |
---|
280 | #+example |
---|
281 | (java:jnew-runtime-class |
---|
282 | "Foo" |
---|
283 | :interfaces (list "java.lang.Comparable") |
---|
284 | :fields (list '("someField" "java.lang.String") '("anotherField" "java.lang.Object" :getter t)) |
---|
285 | :methods (list |
---|
286 | (list "foo" :void '("java.lang.Object") |
---|
287 | (lambda (this that) (print (list this that))) |
---|
288 | :annotations (list "java.lang.Deprecated" |
---|
289 | '("java.lang.annotation.Retention" |
---|
290 | (:enum "java.lang.annotation.RetentionPolicy" :value "RUNTIME")) |
---|
291 | '("javax.xml.bind.annotation.XmlAttribute" ("required" :value t)) |
---|
292 | '("com.manydesigns.portofino.system.model.users.annotations.RequiresPermissions" |
---|
293 | ("level" |
---|
294 | :enum "com.manydesigns.portofino.model.pages.AccessLevel" |
---|
295 | :value "EDIT") |
---|
296 | ("permissions" :value ("foo" "bar"))))) |
---|
297 | (list "bar" :int '("java.lang.Object") |
---|
298 | (lambda (this that) (print (list this that)) 23)))) |
---|
299 | |
---|
300 | (provide "RUNTIME-CLASS") |
---|