234
|
1
|
;;; obj/meta/stealth.lisp --- Stealth Mixin |
|
2
|
|
|
3
|
;; |
|
4
|
|
|
5
|
;;; Code: |
|
6
|
(in-package :obj/meta/stealth) |
|
7
|
|
|
8
|
;;;; The following hack is due to Gilbert Baumann. It allows us to |
|
9
|
;;;; dynamically mix in classes into a class without the latter being |
|
10
|
;;;; aware of it. |
|
11
|
|
|
12
|
;;; Mixins are not intended to be directly instantiated, and instead are used as superclasses to "normal" classes in order to capture common things between several "normal" classes. |
|
13
|
|
|
14
|
;;; First of all we need to keep track of added mixins, we use a hash |
|
15
|
;;; table here. Better would be to stick this information to the |
|
16
|
;;; victim class itself. |
|
17
|
|
|
18
|
(defvar *stealth-mixins* (make-hash-table)) |
|
19
|
|
|
20
|
(defmacro class-stealth-mixins (class) |
|
21
|
`(gethash ,class *stealth-mixins*)) |
|
22
|
|
|
23
|
(defun add-mixin (mixin-name victim-class) |
|
24
|
"Add the mixin to the superclasses of the victim" |
|
25
|
(ensure-class victim-class |
|
26
|
:direct-superclasses (adjoin mixin-name |
|
27
|
(and (find-class victim-class nil) |
|
28
|
(class-direct-superclasses |
|
29
|
(find-class victim-class))) |
|
30
|
:test #'class-equalp) |
|
31
|
:metaclass (class-of (find-class victim-class))) |
|
32
|
|
|
33
|
;; Register it as a new mixin for the victim class |
|
34
|
(pushnew mixin-name (class-stealth-mixins victim-class)) |
|
35
|
|
|
36
|
;; When one wants to [re]define the victim class the new mixin |
|
37
|
;; should be present too. We do this by 'patching' ensure-class: |
|
38
|
(defmethod ensure-class-using-class :around |
|
39
|
(class (name (eql victim-class)) |
|
40
|
&rest arguments |
|
41
|
&key (direct-superclasses nil direct-superclasses-p) |
|
42
|
&allow-other-keys) |
|
43
|
(cond (direct-superclasses-p |
|
44
|
;; Silently modify the super classes to include our new |
|
45
|
;; mixin. |
|
46
|
(dolist (k (class-stealth-mixins name)) |
|
47
|
(pushnew k direct-superclasses |
|
48
|
:test #'class-equalp)) |
|
49
|
(apply #'call-next-method class name |
|
50
|
:direct-superclasses direct-superclasses |
|
51
|
arguments)) |
|
52
|
(t |
|
53
|
(call-next-method)))) |
|
54
|
mixin-name) |
|
55
|
|
|
56
|
(defmacro define-stealth-mixin (name super-classes victim-class |
|
57
|
&rest for-defclass) |
|
58
|
"Like DEFCLASS but adds the newly defined class to the super classes |
|
59
|
of 'victim-class'." |
|
60
|
`(progn |
|
61
|
;; First define the class we talk about |
|
62
|
(defclass ,name ,super-classes ,@for-defclass) |
|
63
|
;; Add the class to the mixins of the victim |
|
64
|
(add-mixin ',name ',victim-class))) |