r4632: Auto commit for Debian build
[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.63 2003/04/25 05:00:58 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 :sql-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 :initarg :direct-rules
52                  :accessor direct-rules
53                  :documentation "List of rules to fire on slot changes.")
54    (class-id :type integer :initform nil
55              :accessor class-id
56              :documentation "Unique ID for the class")
57    (default-view :initform nil :initarg :default-view :accessor default-view
58                  :documentation "The default view for a class")
59
60    ;; SQL commands
61    (create-table-cmd :initform nil :reader create-table-cmd)
62    (create-indices-cmds :initform nil :reader create-index-cmds)
63    (drop-table-cmd :initform nil :reader drop-table-cmd)
64
65    (views :type list :initform nil :initarg :views :accessor views
66           :documentation "List of views")
67    (rules :type list :initform nil :initarg :rules :accessor rules
68           :documentation "List of rules")
69    )
70   (:documentation "Metaclass for Markup Language classes."))
71
72 (defclass subobject ()
73   ((name-class :type symbol :initform nil :initarg :name-class :reader name-class)
74    (name-slot :type symbol :initform nil :initarg :name-slot :reader name-slot)
75    (lookup :type symbol :initform nil :initarg :lookup :reader lookup)
76    (lookup-keys :type list :initform nil :initarg :lookup-keys
77                 :reader lookup-keys))
78   (:documentation "Contains subobject information"))
79
80
81 (defmethod print-object ((obj subobject) (s stream))
82   (print-unreadable-object (obj s :type t :identity t)
83     (format s "~S" (name obj))))
84
85 (defclass hyperlink ()
86   ((name :type symbol :initform nil :initarg :name :reader name)
87    (lookup
88     ;; The type specifier seems to break sbcl
89     ;; :type (or function symbol)
90     :type t
91     :initform nil :initarg :lookup :reader lookup)
92    (link-parameters :type list :initform nil :initarg :link-parameters
93                     :reader link-parameters)))
94
95 (defmethod print-object ((obj hyperlink) (s stream))
96   (print-unreadable-object (obj s :type t :identity t)
97     (format s "~S" (name obj))))
98
99 #+(or cmu scl sbcl)
100 (defmethod validate-superclass ((class hyperobject-class) (superclass standard-class))
101   t)
102
103 (defmethod finalize-inheritance :after ((cl hyperobject-class))
104   (init-hyperobject-class cl)
105   )
106
107 ;; Slot definitions
108 (defmethod direct-slot-definition-class ((cl hyperobject-class) 
109                                          #+allegro &rest
110                                          iargs)
111   (find-class 'hyperobject-dsd))
112
113
114                                         ; Slot definitions
115
116 (eval-when (:compile-toplevel :load-toplevel :execute)
117   (defmacro process-class-option (slot-name &optional required)
118     #+lispworks
119     `(defmethod clos:process-a-class-option ((class hyperobject-class)
120                                              (name (eql ,slot-name))
121                                              value)
122       (when (and ,required (null value))
123         (error "hyperobject class slot ~A must have a value" name))
124       (list name `',value))
125     #+(or allegro sbcl cmu scl)
126     (declare (ignore slot-name required))
127     )
128
129   (defmacro process-slot-option (slot-name)
130     #+lispworks
131     `(defmethod clos:process-a-slot-option ((class hyperobject-class)
132                                             (option (eql ,slot-name))
133                                             value
134                                             already-processed-options
135                                             slot)
136       (list* option `',value already-processed-options))
137     #-lispworks
138     (declare (ignore slot-name))
139     )
140   
141   (dolist (option *class-options*)
142     (eval `(process-class-option ,option)))
143   (dolist (option *slot-options*)
144     (eval `(process-slot-option ,option)))
145
146   (eval
147    `(defclass hyperobject-dsd (standard-direct-slot-definition)
148      (,@(mapcar #'(lambda (x)
149                     `(,(intern (symbol-name x))
150                       :initform nil))
151                 *slot-options-no-initarg*)
152       ,@(mapcar #'(lambda (x)
153                     `(,(intern (symbol-name x))
154                       :initarg
155                       ,(intern (symbol-name x) (symbol-name :keyword))
156                       :initform nil
157                       :accessor
158                       ,(intern (concatenate 'string
159                                             (symbol-name :dsd-)
160                                             (symbol-name x)))))
161                 *slot-options*))))
162   (eval
163    `(defclass hyperobject-esd (standard-effective-slot-definition)
164      (,@(mapcar #'(lambda (x)
165                     `(,(intern (symbol-name x))
166                       :initarg
167                       ,(intern (symbol-name x) (symbol-name :keyword))
168                       :initform nil
169                       :accessor
170                       ,(intern (concatenate 'string
171                                             (symbol-name :esd-)
172                                             (symbol-name x)))))
173                 (append *slot-options* *slot-options-no-initarg*)))))
174   ) ;; eval-when
175
176 (defun intern-in-keyword (obj)
177   (cond
178     ((null obj)
179      nil)
180     ((eq t obj)
181      t)
182     ((atom obj)
183      (intern (symbol-name obj) (find-package 'keyword)))
184     ((consp obj)
185      (cons (intern-in-keyword (car obj) ) (intern-in-keyword (cdr obj))))
186     (t
187      obj)))
188
189 (defun canonicalize-value-type (vt)
190   (typecase vt
191     (atom
192      (ensure-keyword vt))
193     (cons
194      (cons (ensure-keyword (car vt)) (cdr vt)))
195     (t
196      t)))
197
198 (eval-when (:compile-toplevel :load-toplevel :execute)
199   (when (>= (length (generic-function-lambda-list
200                      (ensure-generic-function
201                       'compute-effective-slot-definition)))
202             3)
203     (push :ho-named-cesd-fun cl:*features*)))
204
205 (defmethod compute-effective-slot-definition :around ((cl hyperobject-class)
206                                                       #+ho-named-cesd-fun name
207                                                       dsds)
208   #+allegro (declare (ignore name))
209   (let* ((dsd (car dsds))
210          (value-type (canonicalize-value-type (slot-value dsd 'value-type))))
211     (multiple-value-bind (sql-type length) (value-type-to-sql-type value-type)
212       (setf (slot-value dsd 'sql-type) sql-type)
213       (setf (slot-value dsd 'type) (value-type-to-lisp-type value-type))
214       (let ((ia (compute-effective-slot-definition-initargs cl #+lispworks name dsds)))
215         (apply
216          #'make-instance 'hyperobject-esd 
217          :value-type value-type
218          :sql-type sql-type
219          :length length
220          :print-formatter (slot-value dsd 'print-formatter)
221          :subobject (slot-value dsd 'subobject)
222          :hyperlink (slot-value dsd 'hyperlink)
223          :hyperlink-parameters (slot-value dsd 'hyperlink-parameters)
224          :description (slot-value dsd 'description)
225          :user-name (slot-value dsd 'user-name)
226          :index (slot-value dsd 'index)
227          :value-constraint (slot-value dsd 'value-constraint)
228          :null-allowed (slot-value dsd 'null-allowed)
229          ia)))))
230
231
232 #+ho-named-cesd-fun
233 (setq cl:*features* (delete :ho-named-cesd-fun cl:*features*))
234
235 (defun value-type-to-lisp-type (value-type)
236   (case (if (atom value-type)
237             value-type
238             (car value-type))
239     ((:string :cdata :varchar :char)
240      '(or null string))
241     (:character
242      '(or null character))
243     (:fixnum
244      '(or null fixnum))
245     (:boolean
246      '(or null boolean))
247     (:integer
248      '(or null integer))
249     ((:float :single-float)
250      '(or null single-float))
251     (:double-float
252      '(or null double-float))
253     (otherwise
254      t)))
255
256 (defun value-type-to-sql-type (value-type)
257   "Return two values, the sql type and field length."
258   (let ((type (if (atom value-type)
259                   value-type
260                   (car value-type)))
261         (length (when (consp value-type)
262                   (cadr value-type))))
263     (values
264      (case type
265        ((:string :cdata)
266         :string)
267        ((:fixnum :integer)
268         :integer)
269        (:boolean
270         :boolean)
271        ((:float :single-float)
272         :single-float)
273        (:double-float
274         :double-float)
275        (otherwise
276         :text))
277      length)))
278
279 ;;;; Class initialization function
280
281 ;; defines a slot-unbound method for class and slot-name, fills
282 ;; the slot by calling reader function with the slot values of
283 ;; the instance's reader-keys
284 (defmacro def-lazy-reader (class slot-name reader &rest reader-keys)
285   (let* ((the-slot-name (gensym))
286          (the-class (gensym))
287          (the-instance (gensym))
288          (keys '()))
289     (dolist (key reader-keys)
290       (push (list 'slot-value the-instance (list 'quote key)) keys))
291     (setq keys (nreverse keys))
292     `(defmethod slot-unbound (,the-class (,the-instance ,class)
293                               (,the-slot-name (eql ',slot-name)))
294       (declare (ignore ,the-class))
295       (setf (slot-value ,the-instance ,the-slot-name) (,reader ,@keys)))))
296
297
298 #+lispworks
299 (defun intern-eql-specializer (slot)
300   `(eql ,slot))
301
302 #+(or sbcl cmu lispworks)
303 (defun ensure-lazy-reader (class-name slot-name reader &rest reader-keys)
304   (let ((keys nil)
305         (gf (ensure-generic-function 'slot-unbound)))
306     (dolist (key reader-keys)
307       (push (list 'slot-value 'the-instance (list 'quote key)) keys))
308     (setq keys (nreverse keys))
309     (multiple-value-bind (method-lambda init-args-values)
310         (make-method-lambda
311          gf
312          (class-prototype (generic-function-method-class gf))
313          #-lispworks
314          `(lambda (the-class the-instance the-slot-name)
315            (declare (ignore the-class))
316            (setf (slot-value the-instance the-slot-name) (,reader ,@keys)))
317          #+lispworks
318          '(the-class the-instance the-slot-name)
319          #+lispworks
320          nil
321          #+lispworks
322          `(setf (slot-value the-instance the-slot-name) (,reader ,@keys))
323          nil)
324       (add-method gf
325                   (apply
326                    #'make-instance (generic-function-method-class gf)
327                    ':specializers (list (class-of (find-class class-name))
328                                         (find-class class-name)
329                                         (intern-eql-specializer slot-name))
330                    ':lambda-list '(the-class the-instance the-slot-name)
331                    ':function (compile nil method-lambda)
332                    init-args-values)))))
333
334 (defun finalize-subobjects (cl)
335   "Process class subobjects slot"
336   (setf (subobjects cl)
337         (let ((subobjects '()))
338           (dolist (slot (class-slots cl))
339             (let-when (subobj-def (esd-subobject slot))
340                       (let ((subobject (make-instance 'subobject
341                                                       :name-class (class-name cl)
342                                                       :name-slot (slot-definition-name slot)
343                                                       :lookup (if (atom subobj-def)
344                                                                   subobj-def
345                                                                   (car subobj-def))
346                                                       :lookup-keys (if (atom subobj-def)
347                                                                        nil
348                                                                        (cdr subobj-def)))))
349                         (unless (eq (lookup subobject) t)
350                           #-(or sbcl cmu lispworks)
351                           (eval
352                            `(hyperobject::def-lazy-reader ,(name-class subobject)
353                              ,(name-slot subobject) ,(lookup subobject)
354                              ,@(lookup-keys subobject)))
355                           #+(or sbcl cmu lispworks)
356                           (apply #'ensure-lazy-reader 
357                                  (name-class subobject) (name-slot subobject) (lookup subobject) (lookup-keys subobject))
358                           )
359                         (push subobject subobjects))))
360           ;; sbcl/cmu reverse class-slots compared to the defclass form
361           ;; subobject is already reversed from the dolist/push loop, so re-reverse on cmu/sbcl
362           #+(or cmu sbcl) subobjects
363           #-(or cmu sbcl) (nreverse subobjects)
364           )))
365
366 (defun finalize-documentation (cl)
367   "Calculate class documentation slot"
368   (awhen (slot-value cl 'user-name)
369          (setf (slot-value cl 'user-name)
370                (etypecase (slot-value cl 'user-name)
371                  (cons (car it))
372                  ((or string symbol) it))))
373   (awhen (slot-value cl 'description)
374          (setf (slot-value cl 'description)
375                (etypecase (slot-value cl 'description)
376                  (cons (car it))
377                  ((or string symbol) it))))
378
379   (let ((*print-circle* nil))
380     (setf (documentation (class-name cl) 'class)
381           (format nil "Hyperobject~A~A~A~A"
382                   (aif (user-name cl)
383                        (format nil ": ~A" it ""))
384                   (aif (description cl)
385                        (format nil "~%Class description: ~A" it) "")
386                   (aif (subobjects cl)
387                        (format nil "~%Subobjects:~{ ~A~}" (mapcar #'name-slot it)) "")
388                   (aif (default-print-slots cl)
389                        (format nil "~%Default print slots:~{ ~A~}" it) "")
390                   ))))
391
392 (defun finalize-hyperlinks (cl)
393   (let ((hyperlinks '()))
394     (dolist (esd (class-slots cl))
395       (awhen (slot-value esd 'hyperlink)
396              (push
397               (make-instance 'hyperlink
398                              :name (slot-definition-name esd)
399                              :lookup it
400                              :link-parameters (slot-value esd 'hyperlink-parameters))
401               hyperlinks)))
402     ;; cmu/sbcl reverse class-slots compared to the defclass form
403     ;; hyperlinks is already reversed from the dolist/push loop, so re-reverse on sbcl/cmu
404     #-(or cmu sbcl) (setq hyperlinks (nreverse hyperlinks))
405     (setf (slot-value cl 'hyperlinks) hyperlinks)))
406
407 (defun init-hyperobject-class (cl)
408   "Initialize a hyperobject class. Calculates all class slots"
409   (finalize-subobjects cl)
410   (finalize-views cl)
411   (finalize-hyperlinks cl)
412   (finalize-sql cl)
413   (finalize-rules cl)
414   (finalize-documentation cl))
415
416
417 ;;;; *************************************************************************
418 ;;;;  Metaclass Slot Accessors
419 ;;;; *************************************************************************
420
421 (defun find-slot-by-name (cl name)
422   (find name (class-slots cl) :key #'slot-definition-name))
423
424 (defun hyperobject-class-user-name (obj)
425   (awhen (user-name (class-of obj))
426          (if (consp it)
427              (car it)
428              it)))
429
430 (defun hyperobject-class-subobjects (obj)
431   (subobjects (class-of obj)))
432
433 (defun hyperobject-class-hyperlinks (obj)
434   (hyperlinks (class-of obj)))
435
436 (defun hyperobject-class-slots (obj)
437   ;; cmucl/sbcl reverse class-slots
438   #+(or cmu sbcl) (reverse (class-slots (class-of obj)))
439   #-(or cmu sbcl) (class-slots (class-of obj)))
440