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