| 1 | ; Copyright (c) Rich Hickey. All rights reserved. |
|---|
| 2 | ; The use and distribution terms for this software are covered by the |
|---|
| 3 | ; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) |
|---|
| 4 | ; which can be found in the file CPL.TXT at the root of this distribution. |
|---|
| 5 | ; By using this software in any fashion, you are agreeing to be bound by |
|---|
| 6 | ; the terms of this license. |
|---|
| 7 | ; You must not remove this notice, or any other, from this software. |
|---|
| 8 | |
|---|
| 9 | ; Ported to ABCL by asimon@math.bme.hu. |
|---|
| 10 | ; Minor ABCL fixes by: |
|---|
| 11 | ; A. Vodonosov (avodonosov@yandex.ru). |
|---|
| 12 | ; Alex Mizrahi (alex.mizrahi@gmail.com) |
|---|
| 13 | |
|---|
| 14 | (defpackage :jfli |
|---|
| 15 | (:use :common-lisp :java) |
|---|
| 16 | (:export |
|---|
| 17 | |
|---|
| 18 | :enable-java-proxies |
|---|
| 19 | |
|---|
| 20 | ;wrapper generation |
|---|
| 21 | :def-java-class |
|---|
| 22 | :get-jar-classnames |
|---|
| 23 | :dump-wrapper-defs-to-file |
|---|
| 24 | |
|---|
| 25 | ;object creation etc |
|---|
| 26 | :find-java-class |
|---|
| 27 | :new |
|---|
| 28 | :make-new |
|---|
| 29 | :make-typed-ref |
|---|
| 30 | :jeq |
|---|
| 31 | |
|---|
| 32 | ;array support |
|---|
| 33 | :make-new-array |
|---|
| 34 | :jlength |
|---|
| 35 | :jref |
|---|
| 36 | :jref-boolean |
|---|
| 37 | :jref-byte |
|---|
| 38 | :jref-char |
|---|
| 39 | :jref-double |
|---|
| 40 | :jref-float |
|---|
| 41 | :jref-int |
|---|
| 42 | :jref-short |
|---|
| 43 | :jref-long |
|---|
| 44 | |
|---|
| 45 | ;proxy support |
|---|
| 46 | :new-proxy |
|---|
| 47 | :unregister-proxy |
|---|
| 48 | |
|---|
| 49 | ;conversions |
|---|
| 50 | :box-boolean |
|---|
| 51 | :box-byte |
|---|
| 52 | :box-char |
|---|
| 53 | :box-double |
|---|
| 54 | :box-float |
|---|
| 55 | :box-integer |
|---|
| 56 | :box-long |
|---|
| 57 | :box-short |
|---|
| 58 | :box-string |
|---|
| 59 | :unbox-boolean |
|---|
| 60 | :unbox-byte |
|---|
| 61 | :unbox-char |
|---|
| 62 | :unbox-double |
|---|
| 63 | :unbox-float |
|---|
| 64 | :unbox-integer |
|---|
| 65 | :unbox-long |
|---|
| 66 | :unbox-short |
|---|
| 67 | :unbox-string |
|---|
| 68 | |
|---|
| 69 | ; :ensure-package |
|---|
| 70 | ; :member-symbol |
|---|
| 71 | ; :class-symbol |
|---|
| 72 | ; :constructor-symbol |
|---|
| 73 | |
|---|
| 74 | :*null* |
|---|
| 75 | :new-class |
|---|
| 76 | :super |
|---|
| 77 | )) |
|---|
| 78 | |
|---|
| 79 | (in-package :jfli) |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | (eval-when (:compile-toplevel :load-toplevel :execute) |
|---|
| 83 | (defun string-append (&rest strings) |
|---|
| 84 | (apply #'concatenate 'string (mapcar #'(lambda (s) (if (symbolp s) (symbol-name s) s)) strings))) |
|---|
| 85 | |
|---|
| 86 | |
|---|
| 87 | (defun intern-and-unexport (string package) |
|---|
| 88 | (multiple-value-bind (symbol status) |
|---|
| 89 | (find-symbol string package) |
|---|
| 90 | (when (and *compile-file-pathname* (eq status :external)) (unexport symbol package)) |
|---|
| 91 | (intern string package))) |
|---|
| 92 | ) |
|---|
| 93 | |
|---|
| 94 | (defun is-assignable-from (class-1 class-2) |
|---|
| 95 | (jcall (jmethod "java.lang.Class" "isAssignableFrom" "java.lang.Class") |
|---|
| 96 | class-2 class-1)) ;;not a typo |
|---|
| 97 | |
|---|
| 98 | #+abcl_not_used |
|---|
| 99 | (defun new-object-array (len element-type initial-element) |
|---|
| 100 | (jnew-array-from-array element-type (make-array (list len) :initial-element initial-element))) |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | (defun java-ref-p (x) |
|---|
| 104 | (java-object-p x)) |
|---|
| 105 | |
|---|
| 106 | (deftype java-ref () |
|---|
| 107 | '(satisfies java-ref-p)) |
|---|
| 108 | |
|---|
| 109 | (defun split-package-and-class (name) |
|---|
| 110 | (let ((p (position #\. name :from-end t))) |
|---|
| 111 | (unless p (error "must supply package-qualified classname")) |
|---|
| 112 | (values (subseq name 0 p) |
|---|
| 113 | (subseq name (1+ p))))) |
|---|
| 114 | |
|---|
| 115 | (defun is-name-of-primitive (s) |
|---|
| 116 | (member s '("boolean" "byte" "char" "short" "int" "long" "float" "double" "void") |
|---|
| 117 | :test #'string-equal)) |
|---|
| 118 | |
|---|
| 119 | (defun is-primitive-class (class) |
|---|
| 120 | (is-name-of-primitive (jclass-name class))) |
|---|
| 121 | |
|---|
| 122 | (defun convert-to-java-string (s) |
|---|
| 123 | (jnew (jconstructor "java.lang.String" "java.lang.String") s)) |
|---|
| 124 | |
|---|
| 125 | (define-symbol-macro boolean.type (jfield "java.lang.Boolean" "TYPE")) |
|---|
| 126 | (define-symbol-macro byte.type (jfield "java.lang.Byte" "TYPE")) |
|---|
| 127 | (define-symbol-macro character.type (jfield "java.lang.Character" "TYPE")) |
|---|
| 128 | (define-symbol-macro short.type (jfield "java.lang.Short" "TYPE")) |
|---|
| 129 | (define-symbol-macro integer.type (jfield "java.lang.Integer" "TYPE")) |
|---|
| 130 | (define-symbol-macro long.type (jfield "java.lang.Long" "TYPE")) |
|---|
| 131 | (define-symbol-macro float.type (jfield "java.lang.Float" "TYPE")) |
|---|
| 132 | (define-symbol-macro double.type (jfield "java.lang.Double" "TYPE")) |
|---|
| 133 | (define-symbol-macro void.type (jfield "java.lang.Void" "TYPE")) |
|---|
| 134 | |
|---|
| 135 | #| |
|---|
| 136 | (defconstant boolean.type (jfield "java.lang.Boolean" "TYPE")) |
|---|
| 137 | (defconstant byte.type (jfield "java.lang.Byte" "TYPE")) |
|---|
| 138 | (defconstant character.type (jfield "java.lang.Character" "TYPE")) |
|---|
| 139 | (defconstant short.type (jfield "java.lang.Short" "TYPE")) |
|---|
| 140 | (defconstant integer.type (jfield "java.lang.Integer" "TYPE")) |
|---|
| 141 | (defconstant long.type (jfield "java.lang.Long" "TYPE")) |
|---|
| 142 | (defconstant float.type (jfield "java.lang.Float" "TYPE")) |
|---|
| 143 | (defconstant double.type (jfield "java.lang.Double" "TYPE")) |
|---|
| 144 | |# |
|---|
| 145 | |
|---|
| 146 | (defconstant *null* java:+null+) |
|---|
| 147 | |
|---|
| 148 | (defun identity-or-nil (obj) |
|---|
| 149 | (unless (equal obj *null*) obj)) |
|---|
| 150 | |
|---|
| 151 | ;;;;;;;;;;;;;;;;;;;;;;;;;;; utilities ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 152 | |
|---|
| 153 | (eval-when (:compile-toplevel :load-toplevel :execute) |
|---|
| 154 | (defun ensure-package (name) |
|---|
| 155 | "find the package or create it if it doesn't exist" |
|---|
| 156 | (or (find-package name) |
|---|
| 157 | (make-package name :use '()))) |
|---|
| 158 | (intern "Object" (ensure-package "java.lang")) |
|---|
| 159 | (intern "String" (ensure-package "java.lang"))) |
|---|
| 160 | |
|---|
| 161 | (defun enumeration.hasmoreelements (enum) |
|---|
| 162 | (jcall (jmethod "java.util.Enumeration" "hasMoreElements") enum)) |
|---|
| 163 | |
|---|
| 164 | (defun enumeration.nextelement (enum) |
|---|
| 165 | (jcall (jmethod "java.util.Enumeration" "nextElement") enum)) |
|---|
| 166 | |
|---|
| 167 | (defmacro doenum ((e enum) &body body) |
|---|
| 168 | "jni-based, so not safe and not exported, but used by the implementation" |
|---|
| 169 | (let ((genum (gensym))) |
|---|
| 170 | `(let ((,genum ,enum)) |
|---|
| 171 | (do () |
|---|
| 172 | ((not (enumeration.hasmoreelements ,genum))) |
|---|
| 173 | (let ((,e (enumeration.nextelement ,genum))) |
|---|
| 174 | ,@body))))) |
|---|
| 175 | |
|---|
| 176 | ;probably insufficiently general, works as used here |
|---|
| 177 | (defmacro get-or-init (place init-form) |
|---|
| 178 | `(or ,place |
|---|
| 179 | (setf ,place ,init-form))) |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | (eval-when (:compile-toplevel) |
|---|
| 183 | (intern-and-unexport "OBJECT." "java.lang")) |
|---|
| 184 | |
|---|
| 185 | ;create object. to bootstrap the hierarchy |
|---|
| 186 | (defclass |java.lang|::object. () |
|---|
| 187 | ((ref :reader ref :initarg :ref) |
|---|
| 188 | (lisp-allocated :reader lisp-allocated-p :initarg :lisp-allocated :initform nil)) |
|---|
| 189 | (:documentation "the superclass of all Java typed reference classes")) |
|---|
| 190 | |
|---|
| 191 | (defun get-ref (x) |
|---|
| 192 | "any function taking an object can be passed a raw java-ref ptr or a typed reference instance. |
|---|
| 193 | Will also convert strings for use as objects" |
|---|
| 194 | ;; avodonosov: |
|---|
| 195 | ;; typecase instead of etypecase |
|---|
| 196 | ;; to allow not only jfli-wrapped objects |
|---|
| 197 | ;; as a parameters of NEW-CLASS, but also native |
|---|
| 198 | ;; Lisp objects too (in case of ABCL they are java |
|---|
| 199 | ;; instances anyway). |
|---|
| 200 | ;; For example that may be org.armedbear.lisp.Function. |
|---|
| 201 | (typecase x |
|---|
| 202 | (java-ref x) |
|---|
| 203 | (|java.lang|::object. (ref x)) |
|---|
| 204 | (string (convert-to-java-string x)) |
|---|
| 205 | (null nil) |
|---|
| 206 | ((or number character) x) |
|---|
| 207 | ;; avodonosov: otherwise clause |
|---|
| 208 | (otherwise x))) |
|---|
| 209 | |
|---|
| 210 | (defun is-same-object (obj1 obj2) |
|---|
| 211 | (equal obj1 obj2)) |
|---|
| 212 | |
|---|
| 213 | (defun jeq (obj1 obj2) |
|---|
| 214 | "are these 2 java objects the same object? Note that is not the same as Object.equals()" |
|---|
| 215 | (is-same-object (get-ref obj1) (get-ref obj2))) |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | ;;;;;;;;;;;;;;;;;;;;;;;; names and symbols ;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 219 | #| |
|---|
| 220 | The library does a lot with names and symbols, needing at various times to: |
|---|
| 221 | - find stuff in Java - full names w/case required |
|---|
| 222 | - create hopefully non-conflicting packages and member names |
|---|
| 223 | |
|---|
| 224 | When you (def-java-class "java.lang.String") you get a bunch of symbols/names: |
|---|
| 225 | a package named '|java.lang| |
|---|
| 226 | a class-symbol '|java.lang|:STRING. (note the dot and case), |
|---|
| 227 | which can usually be used where a typename is required |
|---|
| 228 | it also serves as the name of the Lisp typed reference class for string |
|---|
| 229 | its symbol-value is the canonic-class-symbol (see below) |
|---|
| 230 | a canonic-class-symbol '|java.lang|::|String| |
|---|
| 231 | can be used to reconstitute the full class name |
|---|
| 232 | |
|---|
| 233 | I've started trying to flesh out the notion of a Java class designator, which can either be |
|---|
| 234 | the full class name as a string, the class-symbol, or one of :boolean, :int etc |
|---|
| 235 | |# |
|---|
| 236 | |
|---|
| 237 | (defun canonic-class-symbol (full-class-name) |
|---|
| 238 | "(\"java.lang.Object\") -> '|java.lang|:|Object|" |
|---|
| 239 | (multiple-value-bind (package class) (split-package-and-class full-class-name) |
|---|
| 240 | (intern class (ensure-package package)))) |
|---|
| 241 | |
|---|
| 242 | (defun class-symbol (full-class-name) |
|---|
| 243 | "(\"java.lang.Object\") -> '|java.lang|:object." |
|---|
| 244 | (multiple-value-bind (package class) (split-package-and-class full-class-name) |
|---|
| 245 | (intern (string-upcase (string-append class ".")) (ensure-package package)))) |
|---|
| 246 | |
|---|
| 247 | (defun unexported-class-symbol (full-class-name) |
|---|
| 248 | "(\"java.lang.Object\") -> '|java.lang|::object." |
|---|
| 249 | (multiple-value-bind (package class) (split-package-and-class full-class-name) |
|---|
| 250 | (intern-and-unexport (string-upcase (string-append class ".")) (ensure-package package)))) |
|---|
| 251 | |
|---|
| 252 | (defun java-class-name (class-sym) |
|---|
| 253 | "inverse of class-symbol, only valid on class-syms created by def-java-class" |
|---|
| 254 | (let ((canonic-class-symbol (symbol-value class-sym))) |
|---|
| 255 | (string-append (package-name (symbol-package canonic-class-symbol)) |
|---|
| 256 | "." |
|---|
| 257 | canonic-class-symbol))) |
|---|
| 258 | |
|---|
| 259 | (defun member-symbol (full-class-name member-name) |
|---|
| 260 | "members are defined case-insensitively in case-sensitive packages, |
|---|
| 261 | prefixed by 'classname.' - |
|---|
| 262 | (member-symbol \"java.lang.Object\" \"toString\") -> '|java.lang|::OBJECT.TOSTRING" |
|---|
| 263 | (multiple-value-bind (package class) (split-package-and-class full-class-name) |
|---|
| 264 | (intern (string-upcase (string-append class "." member-name)) (ensure-package package)))) |
|---|
| 265 | |
|---|
| 266 | (defun unexported-member-symbol (full-class-name member-name) |
|---|
| 267 | "members are defined case-insensitively in case-sensitive packages, |
|---|
| 268 | prefixed by 'classname.' - |
|---|
| 269 | (member-symbol \"java.lang.Object\" \"toString\") -> '|java.lang|::OBJECT.TOSTRING" |
|---|
| 270 | (multiple-value-bind (package class) (split-package-and-class full-class-name) |
|---|
| 271 | (intern-and-unexport (string-upcase (string-append class "." member-name)) (ensure-package package)))) |
|---|
| 272 | |
|---|
| 273 | (defun constructor-symbol (full-class-name) |
|---|
| 274 | (member-symbol full-class-name "new")) |
|---|
| 275 | |
|---|
| 276 | (defun unexported-constructor-symbol (full-class-name) |
|---|
| 277 | (unexported-member-symbol full-class-name "new")) |
|---|
| 278 | |
|---|
| 279 | (defun get-java-class-ref (canonic-class-symbol) |
|---|
| 280 | "class-ref is cached on the plist of the canonic class symbol" |
|---|
| 281 | (get-or-init (get canonic-class-symbol :class-ref) |
|---|
| 282 | (let ((class-name (string-append (package-name |
|---|
| 283 | (symbol-package canonic-class-symbol)) |
|---|
| 284 | "." |
|---|
| 285 | canonic-class-symbol))) |
|---|
| 286 | (jclass class-name) |
|---|
| 287 | ))) |
|---|
| 288 | |
|---|
| 289 | (defun find-java-class (class-sym-or-string) |
|---|
| 290 | "Given a Java class designator, returns the Java Class object." |
|---|
| 291 | (ctypecase class-sym-or-string |
|---|
| 292 | (symbol (case class-sym-or-string |
|---|
| 293 | (:int integer.type) |
|---|
| 294 | (:char character.type) |
|---|
| 295 | (:long long.type) |
|---|
| 296 | (:float float.type) |
|---|
| 297 | (:boolean boolean.type) |
|---|
| 298 | (:short short.type) |
|---|
| 299 | (:double double.type) |
|---|
| 300 | (:byte byte.type) |
|---|
| 301 | (:void void.type) |
|---|
| 302 | (otherwise (get-java-class-ref class-sym-or-string)))) |
|---|
| 303 | (string (get-java-class-ref (canonic-class-symbol class-sym-or-string))))) |
|---|
| 304 | |
|---|
| 305 | ;;;;;;;;;;;;;;;;;;;;;; typed reference support ;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 306 | #| |
|---|
| 307 | The library maintains a hierarchy of typed reference classes that parallel the |
|---|
| 308 | class hierarchy on the Java side |
|---|
| 309 | new returns a typed reference, but other functions that return objects |
|---|
| 310 | return raw references (for efficiency) |
|---|
| 311 | make-typed-ref can create fully-typed wrappers when desired |
|---|
| 312 | |# |
|---|
| 313 | |
|---|
| 314 | (defun get-superclass-names (full-class-name) |
|---|
| 315 | (let* ((class (get-java-class-ref (canonic-class-symbol full-class-name))) |
|---|
| 316 | (super (jclass-superclass class)) |
|---|
| 317 | (interfaces (jclass-interfaces class)) |
|---|
| 318 | (supers ())) |
|---|
| 319 | (loop for i across interfaces |
|---|
| 320 | do (push i supers)) |
|---|
| 321 | ;hmmm - where should the base class go in the precedence list? |
|---|
| 322 | ;is it more important than the interfaces? this says no |
|---|
| 323 | (if super |
|---|
| 324 | (push super supers) |
|---|
| 325 | (push (jclass "java.lang.Object") supers)) |
|---|
| 326 | (setf supers (nreverse supers)) |
|---|
| 327 | ;now we need to fix up order so more derived classes are first |
|---|
| 328 | ;but don't have a total ordering, so merge one at a time |
|---|
| 329 | (let (result) |
|---|
| 330 | (dolist (s supers) |
|---|
| 331 | (setf result (merge 'list result (list s) |
|---|
| 332 | (lambda (x y) |
|---|
| 333 | (is-assignable-from x y))))) |
|---|
| 334 | (mapcar #'jclass-name result)))) |
|---|
| 335 | #| |
|---|
| 336 | (defun get-superclass-names (full-class-name) |
|---|
| 337 | (let* ((class (get-java-class-ref (canonic-class-symbol full-class-name))) |
|---|
| 338 | (super (class.getsuperclass class)) |
|---|
| 339 | (interfaces (class.getinterfaces class)) |
|---|
| 340 | (supers ())) |
|---|
| 341 | (do-jarray (i interfaces) |
|---|
| 342 | (push (class.getname i) supers)) |
|---|
| 343 | ;hmmm - where should the base class go in the precedence list? |
|---|
| 344 | ;is it more important than the interfaces? this says no |
|---|
| 345 | (if super |
|---|
| 346 | (push (class.getname super) supers) |
|---|
| 347 | (push "java.lang.Object" supers)) |
|---|
| 348 | (nreverse supers))) |
|---|
| 349 | |# |
|---|
| 350 | |
|---|
| 351 | (defun %ensure-java-class (full-class-name) |
|---|
| 352 | "walks the superclass hierarchy and makes sure all the classes are fully defined |
|---|
| 353 | (they may be undefined or just forward-referenced-class) |
|---|
| 354 | caches this has been done on the class-symbol's plist" |
|---|
| 355 | (let* ((class-sym (class-symbol full-class-name)) |
|---|
| 356 | (class (find-class class-sym nil))) |
|---|
| 357 | (if (or (eql class-sym '|java.lang|::object.) |
|---|
| 358 | (get class-sym :ensured)) |
|---|
| 359 | class |
|---|
| 360 | (let ((supers (get-superclass-names full-class-name))) |
|---|
| 361 | (dolist (super supers) |
|---|
| 362 | (%ensure-java-class super)) |
|---|
| 363 | (unless (and class (subtypep class 'standard-object)) |
|---|
| 364 | (setf class |
|---|
| 365 | #+abcl |
|---|
| 366 | (mop::ensure-class class-sym :direct-superclasses (mapcar #'(lambda (c) (find-class (class-symbol c))) supers)))) |
|---|
| 367 | (setf (get class-sym :ensured) t) |
|---|
| 368 | class)))) |
|---|
| 369 | |
|---|
| 370 | |
|---|
| 371 | (defun ensure-java-hierarchy (class-sym) |
|---|
| 372 | "Works off class-sym for efficient use in new |
|---|
| 373 | This will only work on class-syms created by def-java-class, |
|---|
| 374 | as it depends upon symbol-value being the canonic class symbol" |
|---|
| 375 | (unless (get class-sym :ensured) |
|---|
| 376 | (%ensure-java-class (java-class-name class-sym)))) |
|---|
| 377 | |
|---|
| 378 | (defun make-typed-ref (java-ref) |
|---|
| 379 | "Given a raw java-ref, determines the full type of the object |
|---|
| 380 | and returns an instance of a typed reference wrapper" |
|---|
| 381 | (when java-ref |
|---|
| 382 | (let ((class (jobject-class java-ref))) |
|---|
| 383 | (if (jclass-array-p class) |
|---|
| 384 | (error "typed refs not supported for arrays (yet)") |
|---|
| 385 | (make-instance (%ensure-java-class (jclass-name class)) :ref java-ref))))) |
|---|
| 386 | |
|---|
| 387 | |
|---|
| 388 | ;;;;;;;;;;;;;;;;;;;;;;;;; Wrapper Generation ;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 389 | #| |
|---|
| 390 | In an effort to reduce the volume of stuff generated when wrapping entire libraries, |
|---|
| 391 | the wrappers just generate minimal stubs, which, if and when invoked at runtime, |
|---|
| 392 | complete the work of building thunking closures, so very little code is generated for |
|---|
| 393 | things never called (Java libraries have huge numbers of symbols). |
|---|
| 394 | Not sure if this approach matters, but that's how it works |
|---|
| 395 | |# |
|---|
| 396 | |
|---|
| 397 | (defmacro def-java-class (full-class-name) |
|---|
| 398 | "Given the package-qualified, case-correct name of a java class, will generate |
|---|
| 399 | wrapper functions for its contructors, fields and methods." |
|---|
| 400 | (multiple-value-bind (pacakge class) (split-package-and-class full-class-name) |
|---|
| 401 | (declare (ignore class)) |
|---|
| 402 | (let* ((class-sym (unexported-class-symbol full-class-name)) |
|---|
| 403 | (defs |
|---|
| 404 | (list* |
|---|
| 405 | #+nil `(format t "!!!!!!!!!!~a~%" ,full-class-name) |
|---|
| 406 | `(ensure-package ,pacakge) |
|---|
| 407 | ;build a path from the simple class symbol to the canonic |
|---|
| 408 | `(defconstant ,class-sym ',(canonic-class-symbol full-class-name)) |
|---|
| 409 | `(export ',class-sym (symbol-package ',class-sym)) |
|---|
| 410 | `(def-java-constructors ,full-class-name) |
|---|
| 411 | `(def-java-methods ,full-class-name) |
|---|
| 412 | `(def-java-fields ,full-class-name) |
|---|
| 413 | (unless (string= full-class-name "java.lang.Object") |
|---|
| 414 | (let* ((supers (mapcar #'unexported-class-symbol (get-superclass-names full-class-name))) |
|---|
| 415 | (super-exports |
|---|
| 416 | (mapcar #'(lambda (class-sym) `(export ',class-sym (symbol-package ',class-sym))) |
|---|
| 417 | supers))) |
|---|
| 418 | (append (mapcar |
|---|
| 419 | (lambda (p) `(ensure-package ,(package-name p))) |
|---|
| 420 | (remove (symbol-package class-sym) |
|---|
| 421 | (remove-duplicates (mapcar #'symbol-package supers)))) |
|---|
| 422 | super-exports |
|---|
| 423 | (list |
|---|
| 424 | `(defclass ,(class-symbol full-class-name) |
|---|
| 425 | ,supers ())))))))) |
|---|
| 426 | `(locally ,@defs)))) |
|---|
| 427 | |
|---|
| 428 | (defun jarfile.new (fn) |
|---|
| 429 | (jnew (jconstructor "java.util.jar.JarFile" "java.lang.String") fn)) |
|---|
| 430 | |
|---|
| 431 | (defun jarfile.entries (jar) |
|---|
| 432 | (jcall (jmethod "java.util.jar.JarFile" "entries") jar)) |
|---|
| 433 | |
|---|
| 434 | (defun zipentry.isdirectory (e) |
|---|
| 435 | (jcall (jmethod "java.util.zip.ZipEntry" "isDirectory") e)) |
|---|
| 436 | |
|---|
| 437 | (defun zipentry.getname (e) |
|---|
| 438 | (jcall (jmethod "java.util.zip.ZipEntry" "getName") e)) |
|---|
| 439 | |
|---|
| 440 | (defun get-jar-classnames (jar-file-name &rest packages) |
|---|
| 441 | "returns a list of strings, packages should be of the form \"java/lang\" |
|---|
| 442 | for recursive lookup and \"java/util/\" for non-recursive" |
|---|
| 443 | (let* ((jar (jarfile.new jar-file-name)) |
|---|
| 444 | (entries (jarfile.entries jar)) |
|---|
| 445 | (names ())) |
|---|
| 446 | (doenum (e entries) |
|---|
| 447 | (unless (zipentry.isdirectory e) |
|---|
| 448 | (let ((ename (zipentry.getname e))) |
|---|
| 449 | (flet ((matches (package) |
|---|
| 450 | (and (eql 0 (search package ename)) |
|---|
| 451 | (or (not (eql #\/ (schar package (1- (length package))))) ;recursive |
|---|
| 452 | (not (find #\/ ename :start (length package))))))) ;non-subdirectory |
|---|
| 453 | (when (and (eql (search ".class" ename) |
|---|
| 454 | (- (length ename) 6)) ;classname |
|---|
| 455 | ;don't grab anonymous inner classes |
|---|
| 456 | (not (and (find #\$ ename) |
|---|
| 457 | (digit-char-p (schar ename (1+ (position #\$ ename)))))) |
|---|
| 458 | (some #'matches packages)) |
|---|
| 459 | (push (nsubstitute #\. #\/ (subseq ename 0 (- (length ename) 6))) |
|---|
| 460 | names)))))) |
|---|
| 461 | names)) |
|---|
| 462 | |
|---|
| 463 | (defun dump-wrapper-defs-to-file (filename classnames) |
|---|
| 464 | "given a list of classnames (say from get-jar-classnames), writes |
|---|
| 465 | calls to def-java-class to a file" |
|---|
| 466 | (with-open-file (s filename :direction :output :if-exists :supersede) |
|---|
| 467 | (dolist (name (sort classnames #'string-lessp)) |
|---|
| 468 | (format s "(def-java-class ~S)~%" name)))) |
|---|
| 469 | |
|---|
| 470 | ;;;;;;;;;;;;;;;;;;;;;;;;; constructors and new ;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 471 | #| |
|---|
| 472 | |
|---|
| 473 | Every non-interface class with a public ctor will get; |
|---|
| 474 | a constructor, classname.new |
|---|
| 475 | a method defined on make-new, ultimately calling classname.new, |
|---|
| 476 | specialized on (the value of) it's class-symbol (e.g. canonic sym) |
|---|
| 477 | |
|---|
| 478 | Note that if the ctor is overloaded, there is just one function (taking a rest arg), |
|---|
| 479 | which handles overload resolution |
|---|
| 480 | |
|---|
| 481 | The new macro expands into a call to make-new |
|---|
| 482 | |# |
|---|
| 483 | |
|---|
| 484 | (defgeneric make-new (class-sym &rest args) |
|---|
| 485 | (:documentation "Allows for definition of before/after methods on ctors. |
|---|
| 486 | The new macro expands into call to this")) |
|---|
| 487 | |
|---|
| 488 | (defun build-ctor-doc-string (name ctors) |
|---|
| 489 | (with-output-to-string (s) |
|---|
| 490 | (dolist (c ctors) |
|---|
| 491 | (format s "~A(~{~#[~;~A~:;~A,~]~})~%" |
|---|
| 492 | name |
|---|
| 493 | (mapcar #'class-name-for-doc (jarray-to-list (jconstructor-params c))))))) |
|---|
| 494 | |
|---|
| 495 | (defmacro def-java-constructors (full-class-name) |
|---|
| 496 | "creates and exports a ctor func classname.new, defines a method of |
|---|
| 497 | make-new specialized on the class-symbol" |
|---|
| 498 | (let ((ctor-list (get-ctor-list full-class-name))) |
|---|
| 499 | (when ctor-list |
|---|
| 500 | (let ((ctor-sym (unexported-constructor-symbol full-class-name)) |
|---|
| 501 | (class-sym (class-symbol full-class-name))) |
|---|
| 502 | `(locally |
|---|
| 503 | (defun ,ctor-sym (&rest args) |
|---|
| 504 | ,(build-ctor-doc-string full-class-name ctor-list) |
|---|
| 505 | (apply #'install-constructors-and-call ,full-class-name args)) |
|---|
| 506 | (export ',ctor-sym (symbol-package ',ctor-sym)) |
|---|
| 507 | (defmethod make-new ((class-sym (eql ,class-sym)) &rest args) |
|---|
| 508 | (apply (function ,ctor-sym) args))))))) |
|---|
| 509 | |
|---|
| 510 | (defun get-ctor-list (full-class-name) |
|---|
| 511 | (let* ((class-sym (canonic-class-symbol full-class-name)) |
|---|
| 512 | (class (get-java-class-ref class-sym)) |
|---|
| 513 | (ctor-array (jclass-constructors class)) |
|---|
| 514 | (ctor-list (jarray-to-list ctor-array))) |
|---|
| 515 | ctor-list)) |
|---|
| 516 | |
|---|
| 517 | (defun install-constructors-and-call (full-class-name &rest args) |
|---|
| 518 | "initially the constructor symbol for a class is bound to this function, |
|---|
| 519 | when first called it will replace itself with the appropriate direct thunk, |
|---|
| 520 | then call the requested ctor - subsequent calls will be direct" |
|---|
| 521 | (install-constructors full-class-name) |
|---|
| 522 | (apply (constructor-symbol full-class-name) args)) |
|---|
| 523 | |
|---|
| 524 | (defun install-constructors (full-class-name) |
|---|
| 525 | (let* ((ctor-list (get-ctor-list full-class-name))) |
|---|
| 526 | (when ctor-list |
|---|
| 527 | (setf (fdefinition (constructor-symbol full-class-name)) |
|---|
| 528 | (make-ctor-thunk ctor-list (class-symbol full-class-name)))))) |
|---|
| 529 | |
|---|
| 530 | (defun make-ctor-thunk (ctors class-sym) |
|---|
| 531 | (if (rest ctors) ;overloaded |
|---|
| 532 | (make-overloaded-ctor-thunk ctors class-sym) |
|---|
| 533 | (make-non-overloaded-ctor-thunk (first ctors) class-sym))) |
|---|
| 534 | |
|---|
| 535 | (defun make-non-overloaded-ctor-thunk (ctor class-sym) |
|---|
| 536 | (let ((arg-boxers (get-arg-boxers (jconstructor-params ctor)))) |
|---|
| 537 | (lambda (&rest args) |
|---|
| 538 | (let ((arglist (build-arglist args arg-boxers))) |
|---|
| 539 | (ensure-java-hierarchy class-sym) |
|---|
| 540 | (make-instance class-sym |
|---|
| 541 | :ref (apply #'jnew ctor arglist) |
|---|
| 542 | :lisp-allocated t))))) |
|---|
| 543 | |
|---|
| 544 | (defun make-overloaded-ctor-thunk (ctors class-sym) |
|---|
| 545 | (let ((thunks (make-ctor-thunks-by-args-length ctors class-sym))) |
|---|
| 546 | (lambda (&rest args) |
|---|
| 547 | (let ((fn (cdr (assoc (length args) thunks)))) |
|---|
| 548 | (if fn |
|---|
| 549 | (apply fn |
|---|
| 550 | args) |
|---|
| 551 | (error "invalid arity")))))) |
|---|
| 552 | |
|---|
| 553 | (defun make-ctor-thunks-by-args-length (ctors class-sym) |
|---|
| 554 | "returns an alist of thunks keyed by number of args" |
|---|
| 555 | (let ((ctors-by-args-length (make-hash-table)) |
|---|
| 556 | (thunks-by-args-length nil)) |
|---|
| 557 | (dolist (ctor ctors) |
|---|
| 558 | (let ((params-len (length (jconstructor-params ctor)))) |
|---|
| 559 | (push ctor (gethash params-len ctors-by-args-length)))) |
|---|
| 560 | (maphash #'(lambda (args-len ctors) |
|---|
| 561 | (push (cons args-len |
|---|
| 562 | (if (rest ctors);truly overloaded |
|---|
| 563 | (make-type-overloaded-ctor-thunk ctors class-sym) |
|---|
| 564 | ;only one ctor with this number of args |
|---|
| 565 | (make-non-overloaded-ctor-thunk (first ctors) class-sym))) |
|---|
| 566 | thunks-by-args-length)) |
|---|
| 567 | ctors-by-args-length) |
|---|
| 568 | thunks-by-args-length)) |
|---|
| 569 | |
|---|
| 570 | (defun make-type-overloaded-ctor-thunk (ctors class-sym) |
|---|
| 571 | "these methods have the same number of args and must be distinguished by type" |
|---|
| 572 | (let ((thunks (mapcar #'(lambda (ctor) |
|---|
| 573 | (list (make-non-overloaded-ctor-thunk ctor class-sym) |
|---|
| 574 | (jarray-to-list (jconstructor-params ctor)))) |
|---|
| 575 | ctors))) |
|---|
| 576 | (lambda (&rest args) |
|---|
| 577 | (block fn |
|---|
| 578 | (let ((arg-types (get-types-of-args args))) |
|---|
| 579 | (dolist (thunk-info thunks) |
|---|
| 580 | (destructuring-bind (thunk param-types) thunk-info |
|---|
| 581 | (when (is-congruent-type-list param-types arg-types) |
|---|
| 582 | (return-from fn (apply thunk args))))) |
|---|
| 583 | (error "No matching constructor")))))) |
|---|
| 584 | |
|---|
| 585 | (defmacro new (class-spec &rest args) |
|---|
| 586 | "new class-spec args |
|---|
| 587 | class-spec -> class-name | (class-name this-name) |
|---|
| 588 | class-name -> \"package.qualified.ClassName\" | classname. |
|---|
| 589 | args -> [actual-arg]* [init-arg-spec]* |
|---|
| 590 | init-arg-spec -> init-arg | (init-arg) |
|---|
| 591 | init-arg -> :settable-field-or-method [params]* value ;note keyword |
|---|
| 592 | | |
|---|
| 593 | .method-name [args]* ;note dot |
|---|
| 594 | |
|---|
| 595 | Creates a new instance of class-name, using make-new generic function, |
|---|
| 596 | then initializes it by setting fields or accessors and/or calling member functions |
|---|
| 597 | If this-name is supplied it will be bound to the newly-allocated object and available |
|---|
| 598 | to the init-args" |
|---|
| 599 | (labels ((mem-sym? (x) |
|---|
| 600 | (or (keywordp x) |
|---|
| 601 | (and (symbolp x) (eql 0 (position #\. (symbol-name x)))))) |
|---|
| 602 | (mem-form? (x) |
|---|
| 603 | (and (listp x) (mem-sym? (first x)))) |
|---|
| 604 | (mem-init? (x) |
|---|
| 605 | (or (mem-sym? x) (mem-form? x))) |
|---|
| 606 | (init-forms (x) |
|---|
| 607 | (if x |
|---|
| 608 | (if (mem-form? (first x)) |
|---|
| 609 | (cons (first x) (init-forms (rest x))) |
|---|
| 610 | (let ((more (member-if #'mem-init? (rest x)))) |
|---|
| 611 | (cons (ldiff x more) (init-forms more))))))) |
|---|
| 612 | (let* ((inits (member-if #'mem-init? args)) |
|---|
| 613 | (real-args (ldiff args inits)) |
|---|
| 614 | (class-atom (if (atom class-spec) |
|---|
| 615 | class-spec |
|---|
| 616 | (first class-spec))) |
|---|
| 617 | (class-sym (if (symbolp class-atom) |
|---|
| 618 | ;(find-symbol (string-append (symbol-name class-atom) ".")) |
|---|
| 619 | class-atom |
|---|
| 620 | (multiple-value-bind (package class) (split-package-and-class class-atom) |
|---|
| 621 | (find-symbol (string-append (string-upcase class) ".") package)))) |
|---|
| 622 | (class-name (subseq (symbol-name class-sym) 0 (1- (length (symbol-name class-sym))))) |
|---|
| 623 | (gthis (gensym))) |
|---|
| 624 | (flet ((expand-init (x) |
|---|
| 625 | (if (keywordp (first x)) ;setf field or property |
|---|
| 626 | `(setf (,(find-symbol (string-append class-name "." (symbol-name (first x)))) |
|---|
| 627 | ,gthis ,@(butlast (rest x))) |
|---|
| 628 | ,@(last (rest x))) |
|---|
| 629 | ;.memfunc |
|---|
| 630 | `(,(find-symbol (string-append class-name (symbol-name (first x)))) |
|---|
| 631 | ,gthis |
|---|
| 632 | ,@(rest x))))) |
|---|
| 633 | `(let* ((,gthis (make-new ,class-sym ,@real-args)) |
|---|
| 634 | ,@(when (listp class-spec) |
|---|
| 635 | `((,(second class-spec) ,gthis)))) |
|---|
| 636 | ,@(mapcar #'expand-init (init-forms inits)) |
|---|
| 637 | ,gthis))))) |
|---|
| 638 | |
|---|
| 639 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Fields ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 640 | |
|---|
| 641 | #| |
|---|
| 642 | all public fields will get a getter function classname.fieldname and a setter - (setf classname.fieldname) |
|---|
| 643 | instance fields take an first arg which is the instance |
|---|
| 644 | static fields also get a symbol-macro *classname.fieldname* |
|---|
| 645 | |# |
|---|
| 646 | |
|---|
| 647 | (defmacro def-java-fields (full-class-name) |
|---|
| 648 | "fields will get a getter function classname.fieldname and a setter - (setf classname.fieldname) |
|---|
| 649 | instance fields take an first arg which is the instance |
|---|
| 650 | static fields also get a symbol-macro *classname.fieldname*" |
|---|
| 651 | (let* ((class-sym (canonic-class-symbol full-class-name)) |
|---|
| 652 | (class (get-java-class-ref class-sym)) |
|---|
| 653 | (fields (jarray-to-list (jclass-fields class))) |
|---|
| 654 | (defs nil)) |
|---|
| 655 | (dolist (field fields) |
|---|
| 656 | (let* ((field-name (jfield-name field)) |
|---|
| 657 | (field-sym (unexported-member-symbol full-class-name field-name)) |
|---|
| 658 | (is-static (jmember-static-p field))) |
|---|
| 659 | (if is-static |
|---|
| 660 | (let ((macsym (intern-and-unexport (string-append "*" (symbol-name field-sym) "*") |
|---|
| 661 | (symbol-package field-sym)))) |
|---|
| 662 | (push `(defun ,field-sym () |
|---|
| 663 | (install-static-field-and-get ,full-class-name ,field-name)) |
|---|
| 664 | defs) |
|---|
| 665 | (push `(defun (setf ,field-sym) (val) |
|---|
| 666 | (install-static-field-and-set ,full-class-name ,field-name val)) |
|---|
| 667 | defs) |
|---|
| 668 | (push `(export ',field-sym (symbol-package ',field-sym)) defs) |
|---|
| 669 | (push `(define-symbol-macro ,macsym (,field-sym)) defs) |
|---|
| 670 | (push `(export ',macsym (symbol-package ',macsym)) defs)) |
|---|
| 671 | (progn |
|---|
| 672 | (push `(defun ,field-sym (obj) |
|---|
| 673 | (install-field-and-get ,full-class-name ,field-name obj)) |
|---|
| 674 | defs) |
|---|
| 675 | (push `(defun (setf ,field-sym) (val obj) |
|---|
| 676 | (install-field-and-set ,full-class-name ,field-name val obj)) |
|---|
| 677 | defs) |
|---|
| 678 | (push `(export ',field-sym (symbol-package ',field-sym)) defs))))) |
|---|
| 679 | `(locally ,@(nreverse defs)))) |
|---|
| 680 | |
|---|
| 681 | (defun install-field-and-get (full-class-name field-name obj) |
|---|
| 682 | (install-field full-class-name field-name) |
|---|
| 683 | (funcall (member-symbol full-class-name field-name) obj)) |
|---|
| 684 | |
|---|
| 685 | (defun install-field-and-set (full-class-name field-name val obj) |
|---|
| 686 | (install-field full-class-name field-name) |
|---|
| 687 | (funcall (fdefinition `(setf ,(member-symbol full-class-name field-name))) val obj)) |
|---|
| 688 | |
|---|
| 689 | (defun install-static-field-and-get (full-class-name field-name) |
|---|
| 690 | (install-field full-class-name field-name) |
|---|
| 691 | (funcall (member-symbol full-class-name field-name))) |
|---|
| 692 | |
|---|
| 693 | (defun install-static-field-and-set (full-class-name field-name val) |
|---|
| 694 | (install-field full-class-name field-name) |
|---|
| 695 | (funcall (fdefinition `(setf ,(member-symbol full-class-name field-name))) val)) |
|---|
| 696 | |
|---|
| 697 | |
|---|
| 698 | (defun install-field (full-class-name field-name) |
|---|
| 699 | (let* ((class-sym (canonic-class-symbol full-class-name)) |
|---|
| 700 | (class (get-java-class-ref class-sym)) |
|---|
| 701 | (field (jclass-field class field-name)) |
|---|
| 702 | (field-sym (member-symbol full-class-name field-name)) |
|---|
| 703 | (is-static (jmember-static-p field)) |
|---|
| 704 | (field-type-name (jclass-name (jfield-type field))) |
|---|
| 705 | (boxer (get-boxer-fn field-type-name)) |
|---|
| 706 | (unboxer (get-unboxer-fn field-type-name))) |
|---|
| 707 | (if is-static |
|---|
| 708 | (progn |
|---|
| 709 | (setf (fdefinition field-sym) |
|---|
| 710 | (lambda () |
|---|
| 711 | (funcall unboxer (jfield-raw class field-name) #+nil (field.get field nil)))) |
|---|
| 712 | (setf (fdefinition `(setf ,field-sym)) |
|---|
| 713 | (lambda (arg) |
|---|
| 714 | (jfield field-name nil |
|---|
| 715 | (get-ref (if (and boxer (not (boxed? arg))) |
|---|
| 716 | (funcall boxer arg) |
|---|
| 717 | arg))) |
|---|
| 718 | arg))) |
|---|
| 719 | (progn |
|---|
| 720 | (setf (fdefinition field-sym) |
|---|
| 721 | (lambda (obj) |
|---|
| 722 | (funcall unboxer (jfield-raw class field-name (get-ref obj)) #+nil(field.get field (get-ref obj))))) |
|---|
| 723 | (setf (fdefinition `(setf ,field-sym)) |
|---|
| 724 | (lambda (arg obj) |
|---|
| 725 | (jfield field-name (get-ref obj) |
|---|
| 726 | (get-ref (if (and boxer (not (boxed? arg))) |
|---|
| 727 | (funcall boxer arg) |
|---|
| 728 | arg))) |
|---|
| 729 | arg)))))) |
|---|
| 730 | |
|---|
| 731 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; methods ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 732 | #| |
|---|
| 733 | defines wrappers for all public methods of the class |
|---|
| 734 | As with ctors, if a method is overloaded a single wrapper is created that handles |
|---|
| 735 | overload resolution. |
|---|
| 736 | The wrappers have the name classname.methodname |
|---|
| 737 | If a method follows the JavaBeans property protocol (i.e. it is called getSomething or isSomething |
|---|
| 738 | and there is a corresponding setSomething, then a (setf classname.methodname) will be defined |
|---|
| 739 | that calls the latter |
|---|
| 740 | |# |
|---|
| 741 | |
|---|
| 742 | (defun class-name-for-doc (class) |
|---|
| 743 | (let ((name (jclass-name class))) |
|---|
| 744 | (if (jclass-array-p class) |
|---|
| 745 | (decode-array-name name) |
|---|
| 746 | name))) |
|---|
| 747 | |
|---|
| 748 | (defun build-method-doc-string (name methods) |
|---|
| 749 | (with-output-to-string (s) |
|---|
| 750 | (dolist (m methods) |
|---|
| 751 | (format s "~A~A ~A(~{~#[~;~A~:;~A,~]~})~%" |
|---|
| 752 | (if (jmember-static-p m) |
|---|
| 753 | "static " |
|---|
| 754 | "") |
|---|
| 755 | (jclass-name (jmethod-return-type m)) |
|---|
| 756 | name |
|---|
| 757 | (mapcar #'class-name-for-doc (jarray-to-list (jmethod-params m))))))) |
|---|
| 758 | |
|---|
| 759 | (defmacro def-java-methods (full-class-name) |
|---|
| 760 | (let ((methods-by-name (get-methods-by-name full-class-name)) |
|---|
| 761 | (defs nil)) |
|---|
| 762 | (maphash (lambda (name methods) |
|---|
| 763 | (let ((method-sym (unexported-member-symbol full-class-name name))) |
|---|
| 764 | (push `(defun ,method-sym (&rest args) |
|---|
| 765 | ,(build-method-doc-string name methods) |
|---|
| 766 | (apply #'install-methods-and-call ,full-class-name ,name args)) |
|---|
| 767 | defs) |
|---|
| 768 | (push `(export ',method-sym (symbol-package ',method-sym)) |
|---|
| 769 | defs) |
|---|
| 770 | ;build setters when finding beans property protocol |
|---|
| 771 | (flet ((add-setter-if (prefix) |
|---|
| 772 | (when (eql 0 (search prefix name)) |
|---|
| 773 | (let ((setname (string-append "set" (subseq name (length prefix))))) |
|---|
| 774 | (when (gethash setname methods-by-name) |
|---|
| 775 | (push `(defun (setf ,method-sym) (val &rest args) |
|---|
| 776 | (progn |
|---|
| 777 | (apply #',(member-symbol full-class-name setname) |
|---|
| 778 | (append args (list val))) |
|---|
| 779 | val)) |
|---|
| 780 | defs)))))) |
|---|
| 781 | (add-setter-if "get") |
|---|
| 782 | (add-setter-if "is")))) |
|---|
| 783 | methods-by-name) |
|---|
| 784 | `(locally ,@(nreverse defs)))) |
|---|
| 785 | |
|---|
| 786 | (defun install-methods-and-call (full-class-name method &rest args) |
|---|
| 787 | "initially all the member function symbols for a class are bound to this function, |
|---|
| 788 | when first called it will replace them with the appropriate direct thunks, |
|---|
| 789 | then call the requested method - subsequent calls via those symbols will be direct" |
|---|
| 790 | (install-methods full-class-name) |
|---|
| 791 | (apply (member-symbol full-class-name method) args)) |
|---|
| 792 | |
|---|
| 793 | (defun decode-array-name (tn) |
|---|
| 794 | (let ((prim (assoc tn |
|---|
| 795 | '(("Z" . "boolean") |
|---|
| 796 | ("B" . "byte") |
|---|
| 797 | ("C" . "char") |
|---|
| 798 | ("S" . "short") |
|---|
| 799 | ("I" . "int") |
|---|
| 800 | ("J" . "long") |
|---|
| 801 | ("F" . "float") |
|---|
| 802 | ("D" . "double") |
|---|
| 803 | ("V" . "void")) |
|---|
| 804 | :test #'string-equal))) |
|---|
| 805 | (if prim |
|---|
| 806 | (rest prim) |
|---|
| 807 | (let ((array-depth (count #\[ tn))) |
|---|
| 808 | (if (= 0 array-depth) |
|---|
| 809 | (subseq tn 1 (1- (length tn))) ;strip leading L and trailing ; |
|---|
| 810 | (with-output-to-string (s) |
|---|
| 811 | (write-string (decode-array-name (subseq tn array-depth)) s) |
|---|
| 812 | (dotimes (x array-depth) |
|---|
| 813 | (write-string "[]" s)))))))) |
|---|
| 814 | |
|---|
| 815 | (defun jarray-to-list (array) |
|---|
| 816 | (coerce array 'list)) |
|---|
| 817 | |
|---|
| 818 | |
|---|
| 819 | (defun jmethod-made-accessible (method) |
|---|
| 820 | "Return a method made accessible" |
|---|
| 821 | (jcall (jmethod "java.lang.reflect.AccessibleObject" "setAccessible" "boolean") |
|---|
| 822 | method |
|---|
| 823 | java:+true+) |
|---|
| 824 | method) |
|---|
| 825 | |
|---|
| 826 | (defun jclass-relevant-methods (class) |
|---|
| 827 | "Return all public methods, and all protected declared methods" |
|---|
| 828 | (append (jarray-to-list (jclass-methods class)) |
|---|
| 829 | (map 'list #'jmethod-made-accessible |
|---|
| 830 | (remove-if-not #'jmember-protected-p (jclass-methods class :declared t))))) |
|---|
| 831 | |
|---|
| 832 | (defun get-methods-by-name (full-class-name) |
|---|
| 833 | "returns an #'equal hashtable of lists of java.lang.Method refs keyed by name" |
|---|
| 834 | (let* ((class-sym (canonic-class-symbol full-class-name)) |
|---|
| 835 | (class (get-java-class-ref class-sym)) |
|---|
| 836 | (methods (jclass-relevant-methods class)) |
|---|
| 837 | (methods-by-name (make-hash-table :test #'equal))) |
|---|
| 838 | (loop for method in methods |
|---|
| 839 | do |
|---|
| 840 | (push method (gethash (jmethod-name method) methods-by-name))) |
|---|
| 841 | methods-by-name)) |
|---|
| 842 | |
|---|
| 843 | (defun install-methods (full-class-name) |
|---|
| 844 | (let ((methods-by-name (get-methods-by-name full-class-name))) |
|---|
| 845 | (maphash |
|---|
| 846 | (lambda (name methods) |
|---|
| 847 | (setf (fdefinition (member-symbol full-class-name name)) |
|---|
| 848 | (make-method-thunk methods))) |
|---|
| 849 | methods-by-name))) |
|---|
| 850 | |
|---|
| 851 | (defun make-method-thunk (methods) |
|---|
| 852 | (if (rest methods) ;overloaded |
|---|
| 853 | (make-overloaded-thunk methods) |
|---|
| 854 | (make-non-overloaded-thunk (first methods)))) |
|---|
| 855 | |
|---|
| 856 | (defun make-non-overloaded-thunk (method) |
|---|
| 857 | (let* ((unboxer-fn (get-unboxer-fn (jclass-name (jmethod-return-type method)))) |
|---|
| 858 | (arg-boxers (get-arg-boxers (jmethod-params method))) |
|---|
| 859 | (is-static (jmember-static-p method)) |
|---|
| 860 | (caller (if is-static #'jstatic-raw #'jcall-raw))) |
|---|
| 861 | (lambda (&rest args) |
|---|
| 862 | (let ((arglist (build-arglist (if is-static args (rest args)) arg-boxers))) |
|---|
| 863 | (funcall unboxer-fn |
|---|
| 864 | (apply caller method |
|---|
| 865 | (if is-static nil (get-ref (first args))) |
|---|
| 866 | arglist)))))) |
|---|
| 867 | |
|---|
| 868 | (defun make-overloaded-thunk (methods) |
|---|
| 869 | (let ((thunks (make-thunks-by-args-length methods))) |
|---|
| 870 | (lambda (&rest args) |
|---|
| 871 | (let ((fn (cdr (assoc (length args) thunks)))) |
|---|
| 872 | (if fn |
|---|
| 873 | (apply fn |
|---|
| 874 | args) |
|---|
| 875 | (error "invalid arity")))))) |
|---|
| 876 | |
|---|
| 877 | (defun make-thunks-by-args-length (methods) |
|---|
| 878 | "returns an alist of thunks keyed by number of args" |
|---|
| 879 | (let ((methods-by-args-length (make-hash-table)) |
|---|
| 880 | (thunks-by-args-length nil)) |
|---|
| 881 | (dolist (method methods) |
|---|
| 882 | (let ((is-static (jmember-static-p method)) |
|---|
| 883 | (params-len (length (jmethod-params method)))) |
|---|
| 884 | (push method (gethash (if is-static params-len (1+ params-len)) |
|---|
| 885 | methods-by-args-length)))) |
|---|
| 886 | (maphash #'(lambda (args-len methods) |
|---|
| 887 | (push (cons args-len |
|---|
| 888 | (if (rest methods);truly overloaded |
|---|
| 889 | (make-type-overloaded-thunk methods) |
|---|
| 890 | ;only one method with this number of args |
|---|
| 891 | (make-non-overloaded-thunk (first methods)))) |
|---|
| 892 | thunks-by-args-length)) |
|---|
| 893 | methods-by-args-length) |
|---|
| 894 | thunks-by-args-length)) |
|---|
| 895 | |
|---|
| 896 | (defun make-type-overloaded-thunk (methods) |
|---|
| 897 | "these methods have the same number of args and must be distinguished by type" |
|---|
| 898 | (let ((thunks (mapcar #'(lambda (method) |
|---|
| 899 | (list (make-non-overloaded-thunk method) |
|---|
| 900 | (jmember-static-p method) |
|---|
| 901 | (jarray-to-list (jmethod-params method)))) |
|---|
| 902 | methods))) |
|---|
| 903 | (lambda (&rest args) |
|---|
| 904 | (block fn |
|---|
| 905 | (let ((arg-types (get-types-of-args args))) |
|---|
| 906 | (dolist (thunk-info thunks) |
|---|
| 907 | (destructuring-bind (thunk is-static param-types) thunk-info |
|---|
| 908 | (when (is-congruent-type-list param-types (if is-static arg-types (rest arg-types))) |
|---|
| 909 | (return-from fn (apply thunk args))))) |
|---|
| 910 | (error "No matching method")))))) |
|---|
| 911 | |
|---|
| 912 | |
|---|
| 913 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;; array support ;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 914 | |
|---|
| 915 | |
|---|
| 916 | (defun jref (array &rest subscripts) |
|---|
| 917 | (apply #'jarray-ref-raw array subscripts)) |
|---|
| 918 | |
|---|
| 919 | |
|---|
| 920 | (defun (setf jref) (val array &rest subscripts) |
|---|
| 921 | (apply #'jarray-set array val subscripts)) |
|---|
| 922 | |
|---|
| 923 | |
|---|
| 924 | |
|---|
| 925 | (eval-when (:compile-toplevel :load-toplevel :execute) |
|---|
| 926 | (defmacro def-refs (&rest types) |
|---|
| 927 | `(locally |
|---|
| 928 | ,@(mapcan |
|---|
| 929 | (lambda (type) |
|---|
| 930 | (let ((ref-sym (intern (string-upcase (string-append "jref-" (symbol-name type)))))) |
|---|
| 931 | (list |
|---|
| 932 | `(defun ,ref-sym (array &rest subscripts) |
|---|
| 933 | ,(format nil "like aref, for Java arrays of ~A, settable" (symbol-name type)) |
|---|
| 934 | (assert (every #'integerp subscripts)) |
|---|
| 935 | (apply #'jarray-ref array subscripts)) |
|---|
| 936 | |
|---|
| 937 | `(defun (setf ,ref-sym) (val array &rest subscripts) |
|---|
| 938 | (assert (every #'integerp subscripts)) |
|---|
| 939 | (apply #'jarray-set array ,(if (eql type 'boolean) '(box-boolean val) 'val) subscripts) |
|---|
| 940 | )))) |
|---|
| 941 | types)))) |
|---|
| 942 | |
|---|
| 943 | ;arrays of primitives have their own accessors |
|---|
| 944 | (def-refs boolean byte char double float int short long) |
|---|
| 945 | |
|---|
| 946 | (defun jlength (array) |
|---|
| 947 | "like length, for Java arrays" |
|---|
| 948 | (jarray-length array)) ;(get-ref array)? |
|---|
| 949 | |
|---|
| 950 | (defgeneric make-new-array (type &rest dimensions) |
|---|
| 951 | (:documentation "generic function, with methods for all Java class designators") |
|---|
| 952 | (:method (type &rest dims) |
|---|
| 953 | (assert (every #'integerp dims)) |
|---|
| 954 | (apply #'jnew-array type dims))) |
|---|
| 955 | |
|---|
| 956 | (defmethod make-new-array ((type symbol) &rest dimensions) |
|---|
| 957 | (apply #'make-new-array (get-java-class-ref type) dimensions)) |
|---|
| 958 | |
|---|
| 959 | (defmethod make-new-array ((type string) &rest dimensions) |
|---|
| 960 | (apply #'make-new-array (find-java-class type) dimensions)) |
|---|
| 961 | |
|---|
| 962 | (defmethod make-new-array ((type (eql :char)) &rest dimensions) |
|---|
| 963 | (apply #'make-new-array character.type dimensions)) |
|---|
| 964 | |
|---|
| 965 | (defmethod make-new-array ((type (eql :int)) &rest dimensions) |
|---|
| 966 | (apply #'make-new-array integer.type dimensions)) |
|---|
| 967 | |
|---|
| 968 | (defmethod make-new-array ((type (eql :boolean)) &rest dimensions) |
|---|
| 969 | (apply #'make-new-array boolean.type dimensions)) |
|---|
| 970 | |
|---|
| 971 | (defmethod make-new-array ((type (eql :double)) &rest dimensions) |
|---|
| 972 | (apply #'make-new-array double.type dimensions)) |
|---|
| 973 | |
|---|
| 974 | (defmethod make-new-array ((type (eql :byte)) &rest dimensions) |
|---|
| 975 | (apply #'make-new-array byte.type dimensions)) |
|---|
| 976 | |
|---|
| 977 | (defmethod make-new-array ((type (eql :float)) &rest dimensions) |
|---|
| 978 | (apply #'make-new-array float.type dimensions)) |
|---|
| 979 | |
|---|
| 980 | (defmethod make-new-array ((type (eql :short)) &rest dimensions) |
|---|
| 981 | (apply #'make-new-array short.type dimensions)) |
|---|
| 982 | |
|---|
| 983 | (defmethod make-new-array ((type (eql :long)) &rest dimensions) |
|---|
| 984 | (apply #'make-new-array long.type dimensions)) |
|---|
| 985 | |
|---|
| 986 | ;;;;;;;;;;;;;;;;;;;;;;;;;; arg/param helpers ;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 987 | |
|---|
| 988 | |
|---|
| 989 | (defun get-arg-boxers (param-types) |
|---|
| 990 | "returns a list with one entry per param, either nil or a function that boxes the arg" |
|---|
| 991 | (loop for param-type across param-types |
|---|
| 992 | collecting (get-boxer-fn (jclass-name param-type)))) |
|---|
| 993 | |
|---|
| 994 | |
|---|
| 995 | |
|---|
| 996 | (defun build-arglist (args arg-boxers) |
|---|
| 997 | (when args |
|---|
| 998 | (loop for arg in args |
|---|
| 999 | for boxer in arg-boxers |
|---|
| 1000 | collecting |
|---|
| 1001 | (get-ref (if (and boxer (not (boxed? arg))) |
|---|
| 1002 | (funcall boxer arg) |
|---|
| 1003 | arg))))) |
|---|
| 1004 | |
|---|
| 1005 | |
|---|
| 1006 | (defun get-types-of-args (args) |
|---|
| 1007 | (let (ret) |
|---|
| 1008 | (dolist (arg args) |
|---|
| 1009 | (push (infer-box-type arg) |
|---|
| 1010 | ret)) |
|---|
| 1011 | (nreverse ret))) |
|---|
| 1012 | |
|---|
| 1013 | (defun is-congruent-type-list (param-types arg-types) |
|---|
| 1014 | (every #'(lambda (arg-type param-type) |
|---|
| 1015 | (if arg-type |
|---|
| 1016 | (is-assignable-from arg-type param-type) |
|---|
| 1017 | ;nil was passed - must be boolean or non-primitive target type |
|---|
| 1018 | (or (not (is-primitive-class param-type)) |
|---|
| 1019 | (jclass-superclass-p boolean.type param-type)))) |
|---|
| 1020 | arg-types param-types)) |
|---|
| 1021 | |
|---|
| 1022 | |
|---|
| 1023 | ;;;;;;;;;;;;;;;;;;;;;;;; argument conversion and boxing ;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 1024 | (defun box-string (s) |
|---|
| 1025 | "Given a string or symbol, returns reference to a Java string" |
|---|
| 1026 | (convert-to-java-string s)) |
|---|
| 1027 | |
|---|
| 1028 | (defun unbox-string (ref &optional delete-local) |
|---|
| 1029 | "Given a reference to a Java string, returns a Lisp string" |
|---|
| 1030 | (declare (ignore delete-local)) |
|---|
| 1031 | (convert-from-java-string (get-ref ref))) |
|---|
| 1032 | |
|---|
| 1033 | |
|---|
| 1034 | |
|---|
| 1035 | (defun get-boxer-fn (class-name) |
|---|
| 1036 | (if (string= class-name "boolean") |
|---|
| 1037 | #'box-boolean |
|---|
| 1038 | nil)) |
|---|
| 1039 | |
|---|
| 1040 | (defun get-boxer-fn-sym (class-name) |
|---|
| 1041 | (if (string= class-name "boolean") |
|---|
| 1042 | 'box-boolean |
|---|
| 1043 | 'identity)) |
|---|
| 1044 | |
|---|
| 1045 | (defun boxed? (x) |
|---|
| 1046 | (or (java-ref-p x) |
|---|
| 1047 | (typep x '|java.lang|::object.))) |
|---|
| 1048 | |
|---|
| 1049 | (defun infer-box-type (x) |
|---|
| 1050 | (cond |
|---|
| 1051 | ((null x) nil) |
|---|
| 1052 | ((boxed? x) (jobject-class (get-ref x))) |
|---|
| 1053 | ((typep x '(integer -2147483648 +2147483647)) integer.type) |
|---|
| 1054 | ((typep x '(integer -9223372036854775808 +9223372036854775807)) long.type) |
|---|
| 1055 | ((numberp x) double.type) |
|---|
| 1056 | ; ((characterp x) character.type) ;;;FIXME!! |
|---|
| 1057 | ((eq x t) boolean.type) |
|---|
| 1058 | ((or (stringp x) (symbolp x)) |
|---|
| 1059 | (get-java-class-ref '|java.lang|::|String|)) |
|---|
| 1060 | (t (error "can't infer box type")))) |
|---|
| 1061 | |
|---|
| 1062 | |
|---|
| 1063 | (defun get-unboxer-fn (class-name) |
|---|
| 1064 | (if (string= class-name "void") |
|---|
| 1065 | #'unbox-void |
|---|
| 1066 | (if (or (is-name-of-primitive class-name) (string= class-name "java.lang.String")) |
|---|
| 1067 | #'jobject-lisp-value |
|---|
| 1068 | #'identity-or-nil))) |
|---|
| 1069 | |
|---|
| 1070 | (defun get-unboxer-fn-sym (class-name) |
|---|
| 1071 | (if (string= class-name "void") |
|---|
| 1072 | 'unbox-void |
|---|
| 1073 | (if (or (is-name-of-primitive class-name) (string= class-name "java.lang.String")) |
|---|
| 1074 | 'jobject-lisp-value |
|---|
| 1075 | 'identity-or-nil))) |
|---|
| 1076 | |
|---|
| 1077 | |
|---|
| 1078 | (defun unbox-void (x &optional delete-local) |
|---|
| 1079 | (declare (ignore x delete-local)) |
|---|
| 1080 | nil) |
|---|
| 1081 | |
|---|
| 1082 | (defun box-void (x) |
|---|
| 1083 | (declare (ignore x)) |
|---|
| 1084 | nil) |
|---|
| 1085 | |
|---|
| 1086 | (defun box-boolean (x) |
|---|
| 1087 | (if x java:+true+ java:+false+)) |
|---|
| 1088 | |
|---|
| 1089 | ;;;;;;;;;;;;;;;;;;;;;;;; proxy support ;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 1090 | |
|---|
| 1091 | (defun enable-java-proxies () |
|---|
| 1092 | t) |
|---|
| 1093 | |
|---|
| 1094 | (defun find-java-class-in-macro (name) |
|---|
| 1095 | (find-java-class |
|---|
| 1096 | (if (symbolp name) |
|---|
| 1097 | (symbol-value name) |
|---|
| 1098 | name))) |
|---|
| 1099 | |
|---|
| 1100 | (defmacro new-proxy (&rest interface-defs) |
|---|
| 1101 | "interface-def -> (interface-name method-defs+) |
|---|
| 1102 | interface-name -> \"package.qualified.ClassName\" | classname. (must name a Java interface type) |
|---|
| 1103 | method-def -> (method-name arg-defs* body) |
|---|
| 1104 | arg-def -> arg-name | (arg-name arg-type) |
|---|
| 1105 | arg-type -> \"package.qualified.ClassName\" | classname. | :primitive |
|---|
| 1106 | method-name -> symbol | string (matched case-insensitively) |
|---|
| 1107 | |
|---|
| 1108 | Creates, registers and returns a Java object that implements the supplied interfaces" |
|---|
| 1109 | (let (defined-method-names) |
|---|
| 1110 | (labels ((process-idefs (idefs) |
|---|
| 1111 | (when (rest idefs) |
|---|
| 1112 | (error "Sorry, only one interface def at a time")) |
|---|
| 1113 | (process-idef (first idefs))) |
|---|
| 1114 | (process-idef (idef) |
|---|
| 1115 | (destructuring-bind (interface-name &rest method-defs) idef |
|---|
| 1116 | (let* ((methods (jclass-methods (find-java-class-in-macro interface-name))) |
|---|
| 1117 | (ret `((find-java-class ,interface-name) |
|---|
| 1118 | ,@(loop for method-def in method-defs appending (process-method-def method-def methods))))) |
|---|
| 1119 | ;;check to make sure every function is defined |
|---|
| 1120 | (loop for method across methods |
|---|
| 1121 | for mname = (jmethod-name method) |
|---|
| 1122 | unless (member mname defined-method-names :test #'string-equal) |
|---|
| 1123 | do |
|---|
| 1124 | (warn (format nil "proxy doesn't define:~%~A" mname))) |
|---|
| 1125 | ret))) |
|---|
| 1126 | (process-method-def (method-def methods) |
|---|
| 1127 | (destructuring-bind (method-name (&rest arg-defs) &body body) method-def |
|---|
| 1128 | (push method-name defined-method-names) |
|---|
| 1129 | (let ((method (matching-method method-name arg-defs methods)) |
|---|
| 1130 | (gargs (gensym))) |
|---|
| 1131 | `(,(jmethod-name method) |
|---|
| 1132 | (lambda (&rest ,gargs) |
|---|
| 1133 | (,(get-boxer-fn-sym (jclass-name (jmethod-return-type method))) |
|---|
| 1134 | (let ,(arg-lets arg-defs |
|---|
| 1135 | (jarray-to-list (jmethod-params method)) |
|---|
| 1136 | gargs |
|---|
| 1137 | 0) |
|---|
| 1138 | ,@body))))))) |
|---|
| 1139 | (arg-lets (arg-defs params gargs idx) |
|---|
| 1140 | (when arg-defs |
|---|
| 1141 | (let ((arg (first arg-defs)) |
|---|
| 1142 | (param (first params))) |
|---|
| 1143 | (cons `(,(if (atom arg) arg (first arg)) |
|---|
| 1144 | (,(get-unboxer-fn-sym (jclass-name param)) |
|---|
| 1145 | (nth ,idx ,gargs))) |
|---|
| 1146 | (arg-lets (rest arg-defs) (rest params) gargs (1+ idx)))))) |
|---|
| 1147 | (matching-method (method-name arg-defs methods) |
|---|
| 1148 | (let (match) |
|---|
| 1149 | (loop for method across methods |
|---|
| 1150 | when (method-matches method-name arg-defs method) |
|---|
| 1151 | do |
|---|
| 1152 | (if match |
|---|
| 1153 | (error (format nil "more than one method matches ~A" method-name)) |
|---|
| 1154 | (setf match method))) |
|---|
| 1155 | (or match (error (format nil "no method matches ~A" method-name))))) |
|---|
| 1156 | (method-matches (method-name arg-defs method) |
|---|
| 1157 | (when (string-equal method-name (jmethod-name method)) |
|---|
| 1158 | (let ((params (jmethod-params method))) |
|---|
| 1159 | (when (= (length arg-defs) (length params)) |
|---|
| 1160 | (is-congruent arg-defs params))))) |
|---|
| 1161 | (is-congruent (arg-defs params) |
|---|
| 1162 | (every (lambda (arg param) |
|---|
| 1163 | (or (atom arg) ;no type spec matches anything |
|---|
| 1164 | (jeq (find-java-class-in-macro (second arg)) param))) |
|---|
| 1165 | arg-defs (jarray-to-list params)))) |
|---|
| 1166 | `(java::%jnew-proxy ,@(process-idefs interface-defs))))) |
|---|
| 1167 | |
|---|
| 1168 | |
|---|
| 1169 | #+nil |
|---|
| 1170 | (defun jrc (class-name super-name interfaces constructors methods fields &optional filename) |
|---|
| 1171 | "A friendlier version of jnew-runtime-class." |
|---|
| 1172 | #+nil (format t "~s~%~s~%~s~%~s~%~s~%~s~%" class-name super-name interfaces constructors methods fields filename) |
|---|
| 1173 | (if (java:jruntime-class-exists-p class-name) |
|---|
| 1174 | (progn |
|---|
| 1175 | (warn "Java class ~a already exists. Redefining methods." class-name) |
|---|
| 1176 | (loop for |
|---|
| 1177 | (argument-types function super-invocation-args) in constructors |
|---|
| 1178 | do |
|---|
| 1179 | (java:jredefine-method class-name nil argument-types function)) |
|---|
| 1180 | (loop for |
|---|
| 1181 | (method-name return-type argument-types function &rest modifiers) |
|---|
| 1182 | in methods |
|---|
| 1183 | do |
|---|
| 1184 | (java:jredefine-method class-name method-name argument-types function))) |
|---|
| 1185 | (java:jnew-runtime-class class-name super-name interfaces constructors methods fields filename))) |
|---|
| 1186 | |
|---|
| 1187 | |
|---|
| 1188 | (defun get-modifiers (member) |
|---|
| 1189 | (jcall (jmethod "java.lang.reflect.Member" "getModifiers") member)) |
|---|
| 1190 | |
|---|
| 1191 | (defun get-modifier-list (member) |
|---|
| 1192 | (let ((mods (get-modifiers member))) |
|---|
| 1193 | (loop for (mod . mod-call) in |
|---|
| 1194 | '(("public" . "isPublic") |
|---|
| 1195 | ("protected" . "isProtected") |
|---|
| 1196 | ("private" . "isPrivate") |
|---|
| 1197 | ("static" . "isStatic") |
|---|
| 1198 | ;("abstract" . "isAbstract") |
|---|
| 1199 | ("final" . "isFinal") |
|---|
| 1200 | ("transient" . "isTransient") |
|---|
| 1201 | ("volatile" . "isVolatile") |
|---|
| 1202 | ("synchronized" . "isSynchronized")) |
|---|
| 1203 | when |
|---|
| 1204 | (jstatic (jmethod "java.lang.reflect.Modifier" mod-call "int") |
|---|
| 1205 | "java.lang.reflect.Modifier" |
|---|
| 1206 | mods) |
|---|
| 1207 | collect mod))) |
|---|
| 1208 | |
|---|
| 1209 | |
|---|
| 1210 | (defun get-java-object (x) |
|---|
| 1211 | (typecase x |
|---|
| 1212 | (|java.lang|::object. (ref x)) |
|---|
| 1213 | (t x))) |
|---|
| 1214 | |
|---|
| 1215 | (defun find-java-class-name-in-macro (c) |
|---|
| 1216 | (etypecase c |
|---|
| 1217 | (symbol (jclass-name (find-java-class (symbol-value c)))) |
|---|
| 1218 | (string c))) |
|---|
| 1219 | |
|---|
| 1220 | #+nil |
|---|
| 1221 | (defmacro new-class (class-name super-and-interface-names constructor-defs method-defs field-defs) |
|---|
| 1222 | "class-name -> string |
|---|
| 1223 | super-and-interface-names -> class-name | (class-name interface-name*) |
|---|
| 1224 | constructor-defs -> (constructor-def*) |
|---|
| 1225 | constructor-def -> (ctr-arg-defs body) |
|---|
| 1226 | /the first form in body may be (super arg-name+); this will call the constructor of the superclass |
|---|
| 1227 | with the listed arguments/ |
|---|
| 1228 | ctr-arg-def -> (arg-name arg-type) |
|---|
| 1229 | method-def -> (method-name return-type access-modifiers arg-defs* body) |
|---|
| 1230 | /access-modifiers may be nil (to get the modifiers from the superclass), a keyword, or |
|---|
| 1231 | a list of keywords/ |
|---|
| 1232 | method-name -> string |
|---|
| 1233 | arg-def -> arg-name | (arg-name arg-type) |
|---|
| 1234 | arg-type -> \"package.qualified.ClassName\" | classname. | :primitive |
|---|
| 1235 | class-name -> \"package.qualified.ClassName\" | classname. |
|---|
| 1236 | interface-name -> \"package.qualified.InterfaceName\" | interfacename. |
|---|
| 1237 | |
|---|
| 1238 | Creates, registers and returns a Java object that implements the supplied interfaces" |
|---|
| 1239 | (let ((this (intern "THIS" *package*)) |
|---|
| 1240 | (defined-method-names)) |
|---|
| 1241 | (labels ((process-ctr-def (ctr-def ctrs) |
|---|
| 1242 | (destructuring-bind ((&rest arg-defs) &body body) |
|---|
| 1243 | ctr-def |
|---|
| 1244 | (let ((ctr-param-names |
|---|
| 1245 | (mapcar |
|---|
| 1246 | #'(lambda (arg-def) (find-java-class-name-in-macro (cadr arg-def))) |
|---|
| 1247 | arg-defs)) |
|---|
| 1248 | ;(ctr-param-names (mapcar #'cadr arg-defs)) |
|---|
| 1249 | (gargs (gensym)) |
|---|
| 1250 | (head (car body)) |
|---|
| 1251 | (sia)) |
|---|
| 1252 | (when (and (consp head) (eq (car head) 'super)) |
|---|
| 1253 | (setq sia (mapcar |
|---|
| 1254 | #'(lambda (arg-name) |
|---|
| 1255 | (1+ (position arg-name arg-defs :key #'car))) |
|---|
| 1256 | (cdr head)) |
|---|
| 1257 | body (cdr body))) |
|---|
| 1258 | `(,ctr-param-names |
|---|
| 1259 | (lambda (&rest ,gargs) |
|---|
| 1260 | (let ,(arg-lets (append arg-defs (list this)) |
|---|
| 1261 | (append |
|---|
| 1262 | ctr-param-names |
|---|
| 1263 | (list class-name)) |
|---|
| 1264 | gargs |
|---|
| 1265 | 0) |
|---|
| 1266 | ,@body)) |
|---|
| 1267 | ,sia)))) |
|---|
| 1268 | (process-method-def (method-def methods) |
|---|
| 1269 | (destructuring-bind (method-name return-type modifiers (&rest arg-defs) &body body) |
|---|
| 1270 | method-def |
|---|
| 1271 | (push method-name defined-method-names) |
|---|
| 1272 | (let* ((method (matching-method method-name arg-defs methods)) |
|---|
| 1273 | (method-params |
|---|
| 1274 | (if method |
|---|
| 1275 | (jarray-to-list (jmethod-params method)) |
|---|
| 1276 | (mapcar #'(lambda (arg-def) (find-java-class-in-macro (cadr arg-def))) arg-defs))) |
|---|
| 1277 | (method-param-names |
|---|
| 1278 | #+nil |
|---|
| 1279 | (if method |
|---|
| 1280 | (mapcar #'jclass-name (jarray-to-list method-params)) |
|---|
| 1281 | (mapcar #'cadr arg-defs)) |
|---|
| 1282 | (mapcar #'jclass-name method-params)) |
|---|
| 1283 | (return-type-name |
|---|
| 1284 | (jclass-name |
|---|
| 1285 | (if method (jmethod-return-type method) (find-java-class-in-macro return-type)))) |
|---|
| 1286 | (modifiers |
|---|
| 1287 | #+nil |
|---|
| 1288 | (if method (get-modifier-list method) '("public")) |
|---|
| 1289 | (cond ((and (null modifiers) method) (get-modifier-list method)) |
|---|
| 1290 | ((symbolp modifiers) (list (string-downcase (symbol-name modifiers)))) |
|---|
| 1291 | ((consp modifiers) (mapcar #'(lambda (m) (string-downcase (symbol-name m))) modifiers)) |
|---|
| 1292 | (t (error (format t "Need to provide modifiers for method ~A" method-name))))) |
|---|
| 1293 | (gargs (gensym))) |
|---|
| 1294 | `(,method-name ,return-type-name ,method-param-names |
|---|
| 1295 | (lambda (&rest ,gargs) |
|---|
| 1296 | ;;(,(get-boxer-fn-sym return-type-name) |
|---|
| 1297 | (get-java-object ;;check! |
|---|
| 1298 | (let ,(arg-lets (append arg-defs (list this)) |
|---|
| 1299 | (append |
|---|
| 1300 | method-param-names |
|---|
| 1301 | #+nil (map 'list #'(lambda (p) (jclass-name p)) method-params) |
|---|
| 1302 | (list class-name)) |
|---|
| 1303 | gargs |
|---|
| 1304 | 0) |
|---|
| 1305 | ,@body)) |
|---|
| 1306 | ) |
|---|
| 1307 | ,@modifiers)))) |
|---|
| 1308 | (arg-lets (arg-defs params gargs idx) |
|---|
| 1309 | (when arg-defs |
|---|
| 1310 | (let ((arg (first arg-defs)) |
|---|
| 1311 | (param (first params))) |
|---|
| 1312 | (cons `(,(if (atom arg) arg (first arg)) |
|---|
| 1313 | (,(get-unboxer-fn-sym param) |
|---|
| 1314 | (nth ,idx ,gargs))) |
|---|
| 1315 | (arg-lets (rest arg-defs) (rest params) gargs (1+ idx)))))) |
|---|
| 1316 | (matching-method (method-name arg-defs methods) |
|---|
| 1317 | (let (match) |
|---|
| 1318 | (loop for method across methods |
|---|
| 1319 | when (method-matches method-name arg-defs method) |
|---|
| 1320 | do |
|---|
| 1321 | (if match |
|---|
| 1322 | (error (format nil "more than one method matches ~A" method-name)) |
|---|
| 1323 | (setf match method))) |
|---|
| 1324 | match)) |
|---|
| 1325 | (method-matches (method-name arg-defs method) |
|---|
| 1326 | (when (string-equal method-name (jmethod-name method)) |
|---|
| 1327 | (let ((params (jmethod-params method))) |
|---|
| 1328 | (when (= (length arg-defs) (length params)) |
|---|
| 1329 | (is-congruent arg-defs params))))) |
|---|
| 1330 | (is-congruent (arg-defs params) |
|---|
| 1331 | (every (lambda (arg param) |
|---|
| 1332 | (or (atom arg) ;no type spec matches anything |
|---|
| 1333 | (jeq (find-java-class-in-macro (second arg)) param))) |
|---|
| 1334 | arg-defs (jarray-to-list params)))) |
|---|
| 1335 | (unless (consp super-and-interface-names) |
|---|
| 1336 | (setq super-and-interface-names (list super-and-interface-names))) |
|---|
| 1337 | (let* ((super-name (find-java-class-name-in-macro (car super-and-interface-names))) |
|---|
| 1338 | (interfaces (mapcar #'find-java-class-name-in-macro (cdr super-and-interface-names))) |
|---|
| 1339 | (super (jclass super-name)) |
|---|
| 1340 | (super-ctrs (jclass-constructors super)) |
|---|
| 1341 | (ctrs-ret (loop for ctr-def in constructor-defs collecting |
|---|
| 1342 | (process-ctr-def ctr-def super-ctrs))) |
|---|
| 1343 | (super-methods (jclass-methods super)) |
|---|
| 1344 | (iface-methods |
|---|
| 1345 | (apply #'concatenate 'vector |
|---|
| 1346 | (mapcar #'(lambda (ifn) |
|---|
| 1347 | (jclass-methods (jclass ifn))) |
|---|
| 1348 | interfaces))) |
|---|
| 1349 | (methods-ret (loop for method-def in method-defs collecting |
|---|
| 1350 | (process-method-def |
|---|
| 1351 | method-def |
|---|
| 1352 | (concatenate 'vector super-methods iface-methods))))) |
|---|
| 1353 | ;;check to make sure every function is defined |
|---|
| 1354 | (loop for method across iface-methods |
|---|
| 1355 | for mname = (jmethod-name method) |
|---|
| 1356 | unless (member mname defined-method-names :test #'string-equal) |
|---|
| 1357 | do |
|---|
| 1358 | (warn (format nil "class doesn't define:~%~A" mname))) |
|---|
| 1359 | `(progn |
|---|
| 1360 | (jrc ,class-name ,super-name ,interfaces |
|---|
| 1361 | ',ctrs-ret |
|---|
| 1362 | ',methods-ret |
|---|
| 1363 | (loop for (fn type . mods) in ',field-defs |
|---|
| 1364 | collecting `(,fn ,(find-java-class-name-in-macro type) |
|---|
| 1365 | ,@(mapcar #'(lambda (mod) (string-downcase (symbol-name mod))) mods))) |
|---|
| 1366 | #+nil ,(namestring (merge-pathnames class-name "/tmp/"))) |
|---|
| 1367 | (eval '(def-java-class ,class-name))))))) |
|---|
| 1368 | |
|---|
| 1369 | |
|---|