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