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