r3623: *** empty log message ***
[hyperobject.git] / mop.lisp
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          mop.lisp
6 ;;;; Purpose:       Metaobject Protocol Interface
7 ;;;; Programmer:    Kevin M. Rosenberg
8 ;;;; Date Started:  Apr 2000
9 ;;;;
10 ;;;; This metaclass as functions to classes to allow display
11 ;;;; in Text, HTML, and XML formats. This includes hyperlinking
12 ;;;; capability and sub-objects.
13 ;;;;
14 ;;;; $Id: mop.lisp,v 1.12 2002/12/13 22:00:05 kevin Exp $
15 ;;;;
16 ;;;; This file is Copyright (c) 2000-2002 by Kevin M. Rosenberg
17 ;;;;
18 ;;;; *************************************************************************
19  
20 (in-package :hyperobject)
21
22 (eval-when (:compile-toplevel :execute)
23   (declaim (optimize (speed 2) (safety 2) (compilation-speed 0) (debug 2))))
24
25 ;; Main class
26
27 (defclass hyperobject-class (standard-class)
28   ( ;; slots initialized in defclass
29    (user-name :initarg :user-name :type string :initform nil
30               :accessor user-name
31               :documentation "User name for class")
32    (default-print-slots :initarg :default-print-slots :type list :initform nil
33                         :accessor default-print-slots
34                         :documentation "Defaults slots for a view")
35    (description :initarg :description :initform nil
36                 :accessor description
37                 :documentation "Class description")
38    (version :initarg :version :initform nil
39             :accessor version
40             :documentation "Version number for class")
41    (sql-name :initarg :table-name :initform nil)
42
43    ;;; The remainder of these fields are calculated one time
44    ;;; in finalize-inheritence.
45    
46    (subobjects :initform nil :accessor subobjects
47                :documentation
48                "List of fields that contain a list of subobjects objects.")
49    (hyperlinks :type list :initform nil :accessor hyperlinks
50                :documentation "List of fields that have hyperlinks")
51    (direct-rules :type list :initform nil :accessor direct-rules
52                  :documentation "List of rules to fire on slot changes.")
53    (class-id :type integer :initform nil
54              :accessor class-id
55              :documentation "Unique ID for the class")
56
57    ;; SQL commands
58    (create-table-cmd :initform nil :reader create-table-cmd)
59    (create-indices-cmds :initform nil :reader create-index-cmds)
60    (drop-table-cmd :initform nil :reader drop-table-cmd)
61
62    (views :type list :initform nil :initarg :views :accessor views
63           :documentation "List of views")
64    (default-view :initform nil :initarg :default-view :accessor default-view
65                  :documentation "The default view for a class")
66    )
67   (:documentation "Metaclass for Markup Language classes."))
68
69 (defclass subobject ()
70   ((name-class :type symbol :initform nil :initarg :name-class :reader name-class)
71    (name-slot :type symbol :initform nil :initarg :name-slot :reader name-slot)
72    (lookup :type symbol :initform nil :initarg :lookup :reader lookup)
73    (lookup-keys :type list :initform nil :initarg :lookup-keys
74                 :reader lookup-keys))
75   (:documentation "Contains subobject information"))
76
77
78 (defmethod print-object ((obj subobject) (s stream))
79   (print-unreadable-object (obj s :type t :identity t)
80     (format s "~S" (name obj))))
81
82 (defclass hyperlink ()
83   ((name :type symbol :initform nil :initarg :name :reader name)
84    (lookup :type function :initform nil :initarg :lookup :reader lookup)
85    (link-parameters :type list :initform nil :initarg :link-parameters
86                     :reader link-parameters)))
87
88 (defmethod print-object ((obj hyperlink) (s stream))
89   (print-unreadable-object (obj s :type t :identity t)
90     (format s "~S" (name obj))))
91
92 #+(or cmu scl sbcl)
93 (defmethod validate-superclass ((class hyperobject-class) (superclass standard-class))
94   t)
95
96 (defmethod finalize-inheritance :after ((cl hyperobject-class))
97   (init-hyperobject-class cl)
98   )
99
100 ;; Slot definitions
101 (defmethod direct-slot-definition-class ((cl hyperobject-class) 
102                                          #+allegro &rest
103                                                    iargs)
104   (find-class 'hyperobject-dsd))
105
106
107 ; Slot definitions
108
109 (eval-when (:compile-toplevel :load-toplevel :execute)
110   (defmacro process-class-option (slot-name &optional required)
111     #+lispworks
112     `(defmethod clos:process-a-class-option ((class hyperobject-class)
113                                              (name (eql ,slot-name))
114                                              value)
115     (when (and ,required (null value))
116       (error "hyperobject class slot ~A must have a value" name))
117     (list name `',value))
118     #+(or allegro sbcl cmu scl)
119     (declare (ignore slot-name required))
120     )
121
122   (defmacro process-slot-option (slot-name)
123     #+lispworks
124     `(defmethod clos:process-a-slot-option ((class hyperobject-class)
125                                             (option (eql ,slot-name))
126                                             value
127                                           already-processed-other-options
128                                             slot)
129        (list option `',value))
130     #-lispworks
131     (declare (ignore slot-name))
132     )
133   
134   (dolist (option *class-options*)
135     (eval `(process-class-option ,option)))
136   (dolist (option *slot-options*)
137     (eval `(process-slot-option ,option)))
138
139   (eval
140    `(defclass hyperobject-dsd (standard-direct-slot-definition)
141       (,@(mapcar #'(lambda (x)
142                      `(,(intern (symbol-name x))
143                         :initform nil))
144                  *slot-options-no-initarg*)
145          ,@(mapcar #'(lambda (x)
146                        `(,(intern (symbol-name x))
147                           :initarg
148                           ,(intern (symbol-name x) (symbol-name :keyword))
149                           :initform nil
150                           :accessor
151                           ,(intern (concatenate 'string
152                                                 (symbol-name :dsd-)
153                                                 (symbol-name x)))))
154                    *slot-options*))))
155   (eval
156    `(defclass hyperobject-esd (standard-effective-slot-definition)
157       (,@(mapcar #'(lambda (x)
158                      `(,(intern (symbol-name x))
159                         :initarg
160                         ,(intern (symbol-name x) (symbol-name :keyword))
161                         :initform nil
162                         :accessor
163                         ,(intern (concatenate 'string
164                                               (symbol-name :esd-)
165                                               (symbol-name x)))))
166                  (append *slot-options* *slot-options-no-initarg*)))))
167   ) ;; eval-when
168
169 (defun intern-in-keyword (obj)
170   (cond
171     ((null obj)
172      nil)
173     ((eq t obj)
174      t)
175     ((atom obj)
176      (intern (symbol-name obj) (find-package 'keyword)))
177     ((consp obj)
178      (cons (intern-in-keyword (car obj) ) (intern-in-keyword (cdr obj))))
179     (t
180      obj)))
181
182 (defmethod compute-effective-slot-definition :around ((cl hyperobject-class)
183                                                       #+(or allegro lispworks) name
184                                                       dsds)
185   #+allergo (declare (ignore name))
186   (let* ((dsd (car dsds))
187          (ho-type (intern-in-keyword (slot-value dsd 'type)))
188          (sql-type (ho-type-to-sql-type ho-type))
189          (length (when (consp ho-type) (cadr ho-type))))
190   #+allergo (declare (ignore name))
191     (setf (slot-value dsd 'ho-type) ho-type)
192     (setf (slot-value dsd 'sql-type) sql-type)
193     (setf (slot-value dsd 'type) (ho-type-to-lisp-type ho-type))
194     (let ((ia (compute-effective-slot-definition-initargs
195                cl #+lispworks name dsds)))
196       (apply
197        #'make-instance 'hyperobject-esd 
198        :ho-type ho-type
199        :sql-type sql-type
200        :length length
201        :print-formatter (slot-value dsd 'print-formatter)
202        :subobject (slot-value dsd 'subobject)
203        :hyperlink (slot-value dsd 'hyperlink)
204        :hyperlink-parameters (slot-value dsd 'hyperlink-parameters)
205        :description (slot-value dsd 'description)
206        :user-name (slot-value dsd 'user-name)
207        :index (slot-value dsd 'index)
208        :value-constraint (slot-value dsd 'value-constraint)
209        :null-allowed (slot-value dsd 'null-allowed)
210        ia))))
211
212 (defun ho-type-to-lisp-type (ho-type)
213   (when (consp ho-type)
214     (setq ho-type (car ho-type)))
215   (check-type ho-type symbol)
216   (case ho-type
217     ((or :string :cdata :varchar :char)
218      'string)
219     (:character
220      'character)
221     (:fixnum
222      'fixnum)
223     (:boolean
224      'boolean)
225     (:integer
226      'integer)
227     ((or :float :single-float)
228      'single-float)
229     (:double-float
230      'double-float)
231     (nil
232      t)
233     (otherwise
234      ho-type)))
235
236 (defun ho-type-to-sql-type (ho-type)
237   (when (consp ho-type)
238     (setq ho-type (car ho-type)))
239   (check-type ho-type symbol)
240   (case ho-type
241     ((or :string :cdata)
242      'string)
243     (:fixnum
244      'integer)
245     (:boolean
246      'boolean)
247     (:integer
248      'integer)
249     ((or :float :single-float)
250      'single-float)
251     (:double-float
252      'double-float)
253     (nil
254      t)
255     (otherwise
256      ho-type)))
257
258
259 ;;;; Class initialization function
260
261 ;; defines a slot-unbound method for class and slot-name, fills
262 ;; the slot by calling reader function with the slot values of
263 ;; the instance's reader-keys
264 (defmacro def-lazy-reader (class slot-name reader &rest reader-keys)
265   (let* ((the-slot-name (gensym))
266          (the-class (gensym))
267          (the-instance (gensym))
268          (keys '()))
269     (dolist (key reader-keys)
270       (push (list 'slot-value the-instance (list 'quote key)) keys))
271     (setq keys (nreverse keys))
272     `(defmethod slot-unbound (,the-class (,the-instance ,class)
273                                          (,the-slot-name (eql ',slot-name)))
274        (declare (ignore ,the-class))
275        (setf (slot-value ,the-instance ,the-slot-name)
276            (,reader ,@keys)))))
277
278 (defun finalize-subobjects (cl)
279   "Process class subobjects slot"
280   (setf (subobjects cl)
281     (let ((subobjects '()))
282       (dolist (slot (class-slots cl))
283         (let-when (subobj-def (esd-subobject slot))
284           (let ((subobject (make-instance 'subobject
285                                           :name-class (class-name cl)
286                                           :name-slot (slot-definition-name slot)
287                                           :lookup (if (atom subobj-def)
288                                                       subobj-def
289                                                       (car subobj-def))
290                                           :lookup-keys (if (atom subobj-def)
291                                                            nil
292                                                            (cdr subobj-def)))))
293             (unless (eq (lookup subobject) t)
294               (eval `(def-lazy-reader ,(name-class subobject)
295                          ,(name-slot subobject) ,(lookup subobject)
296                          ,@(lookup-keys subobject))))
297             (push subobject subobjects))))
298       subobjects)))
299
300 (defun finalize-documentation (cl)
301   "Calculate class documentation slot"
302   (awhen (slot-value cl 'user-name)
303          (setf (slot-value cl 'user-name)
304                (etypecase (slot-value cl 'user-name)
305                    (cons (car it))
306                    ((or string symbol) it))))
307   (awhen (slot-value cl 'description)
308          (setf (slot-value cl 'description)
309                (etypecase (slot-value cl 'description)
310                    (cons (car it))
311                    ((or string symbol) it))))
312
313   (let ((*print-circle* nil))
314     (setf (documentation (class-name cl) 'class)
315       (format nil "Hyperobject~A~A~A~A"
316               (aif (user-name cl)
317                    (format nil ": ~A" it ""))
318               (aif (description cl)
319                    (format nil "~%Class description: ~A" it) "")
320               (aif (subobjects cl)
321                    (format nil "~%Subobjects:~{ ~A~}" (mapcar #'name-slot it)) "")
322               (aif (default-print-slots cl)
323                    (format nil "~%Default print slots:~{ ~A~}" it) "")
324               ))))
325
326 (defun finalize-hyperlinks (cl)
327   (let ((hyperlinks '()))
328     (dolist (esd (class-slots cl))
329       (awhen (slot-value esd 'hyperlink)
330         (push
331          (make-instance 'hyperlink
332                         :name (slot-definition-name esd)
333                         :lookup it
334                         :link-parameters (slot-value esd 'hyperlink-parameters))
335          hyperlinks)))
336     (setf (slot-value cl 'hyperlinks) hyperlinks)))
337
338 (defun init-hyperobject-class (cl)
339   "Initialize a hyperobject class. Calculates all class slots"
340   (finalize-subobjects cl)
341   (finalize-views cl)
342   (finalize-hyperlinks cl)
343   (finalize-sql cl)
344   (finalize-documentation cl))
345
346
347 ;;;; *************************************************************************
348 ;;;;  Metaclass Slot Accessors
349 ;;;; *************************************************************************
350
351 (defun find-slot-by-name (cl name)
352   (find name (class-slots cl) :key #'slot-definition-name))
353
354 (defun hyperobject-class-user-name (obj)
355   (awhen (user-name (class-of obj))
356          (if (consp it)
357              (car it)
358              it)))
359
360 (defun hyperobject-class-subobjects (obj)
361   (subobjects (class-of obj)))
362
363 (defun hyperobject-class-hyperlinks (obj)
364   (hyperlinks (class-of obj)))
365
366 (defun hyperobject-class-fields (obj)
367   (class-slots (class-of obj)))
368
369 ;;; Slot accessor and class rules
370
371 (defun fire-class-rules (cl obj)
372   "Fire all class rules. Called after a slot is modified."
373   (dolist (rule (direct-rules cl))
374     (cmsg "firing rule: ~a" rule)))
375
376
377 (defmethod (setf slot-value-using-class) 
378     :around (new-value (cl hyperobject-class) obj
379              (slot standard-effective-slot-definition))
380   (cmsg-c :verbose "Setf slot value: class: ~s, obj: ~s, slot: ~s, value: ~s" cl (class-of obj) slot new-value)
381   (let ((func (esd-value-constraint slot)))
382     (cond
383      ((and func (not (funcall func new-value obj)))
384       (warn "Rejected change to value of slot ~a of object ~a"
385             (slot-definition-name slot) obj)
386       (slot-value obj (slot-definition-name slot)))
387      (t
388       (call-next-method)
389       (fire-class-rules cl obj)
390       new-value))))
391   
392 (defmethod slot-value-using-class :around ((cl hyperobject-class) obj
393                                            (slot standard-effective-slot-definition))
394   (let ((value (call-next-method)))
395     (cmsg-c :verbose "slot value: class: ~s, obj: ~s, slot: ~s" cl (class-of obj) slot)
396     value))