source: trunk/abcl/src/org/armedbear/lisp/runtime-class.lisp @ 13790

Last change on this file since 13790 was 13790, checked in by astalla, 10 years ago

[runtime-class] added auto getter/setter generation for fields.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.8 KB
Line 
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")
Note: See TracBrowser for help on using the repository browser.