r3615: *** 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.11 2002/12/13 08:25:45 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    (class-id :type integer :initform nil
52              :accessor class-id
53              :documentation "Unique ID for the class")
54
55    ;; SQL commands
56    (create-table-cmd :initform nil :reader create-table-cmd)
57    (create-indices-cmds :initform nil :reader create-index-cmds)
58    (drop-table-cmd :initform nil :reader drop-table-cmd)
59
60    (views :type list :initform nil :initarg :views :accessor views
61           :documentation "List of views")
62    (default-view :initform nil :initarg :default-view :accessor default-view
63                  :documentation "The default view for a class")
64    )
65   (:documentation "Metaclass for Markup Language classes."))
66
67 (defclass subobject ()
68   ((name-class :type symbol :initform nil :initarg :name-class :reader name-class)
69    (name-slot :type symbol :initform nil :initarg :name-slot :reader name-slot)
70    (lookup :type symbol :initform nil :initarg :lookup :reader lookup)
71    (lookup-keys :type list :initform nil :initarg :lookup-keys
72                 :reader lookup-keys))
73   (:documentation "Contains subobject information"))
74
75
76 (defmethod print-object ((obj subobject) (s stream))
77   (print-unreadable-object (obj s :type t :identity t)
78     (format s "~S" (name obj))))
79
80 (defclass hyperlink ()
81   ((name :type symbol :initform nil :initarg :name :reader name)
82    (lookup :type function :initform nil :initarg :lookup :reader lookup)
83    (link-parameters :type list :initform nil :initarg :link-parameters
84                     :reader link-parameters)))
85
86 (defmethod print-object ((obj hyperlink) (s stream))
87   (print-unreadable-object (obj s :type t :identity t)
88     (format s "~S" (name obj))))
89
90 #+(or cmu scl sbcl)
91 (defmethod validate-superclass ((class hyperobject-class) (superclass standard-class))
92   t)
93
94 (defmethod finalize-inheritance :after ((cl hyperobject-class))
95   (init-hyperobject-class cl)
96   )
97
98 ;; Slot definitions
99 (defmethod direct-slot-definition-class ((cl hyperobject-class) 
100                                          #+allegro &rest
101                                                    iargs)
102   (find-class 'hyperobject-dsd))
103
104
105 ; Slot definitions
106
107 (eval-when (:compile-toplevel :load-toplevel :execute)
108   (defmacro process-class-option (slot-name &optional required)
109     #+lispworks
110     `(defmethod clos:process-a-class-option ((class hyperobject-class)
111                                              (name (eql ,slot-name))
112                                              value)
113     (when (and ,required (null value))
114       (error "hyperobject class slot ~A must have a value" name))
115     (list name `',value))
116     #+(or allegro sbcl cmu scl)
117     (declare (ignore slot-name required))
118     )
119
120   (defmacro process-slot-option (slot-name)
121     #+lispworks
122     `(defmethod clos:process-a-slot-option ((class hyperobject-class)
123                                             (option (eql ,slot-name))
124                                             value
125                                           already-processed-other-options
126                                             slot)
127        (list option `',value))
128     #-lispworks
129     (declare (ignore slot-name))
130     )
131   
132   (dolist (option *class-options*)
133     (eval `(process-class-option ,option)))
134   (dolist (option *slot-options*)
135     (eval `(process-slot-option ,option)))
136
137   (eval
138    `(defclass hyperobject-dsd (standard-direct-slot-definition)
139       (,@(mapcar #'(lambda (x)
140                      `(,(intern (symbol-name x))
141                         :initform nil))
142                  *slot-options-no-initarg*)
143          ,@(mapcar #'(lambda (x)
144                        `(,(intern (symbol-name x))
145                           :initarg
146                           ,(intern (symbol-name x) (symbol-name :keyword))
147                           :initform nil
148                           :accessor
149                           ,(intern (concatenate 'string
150                                                 (symbol-name :dsd-)
151                                                 (symbol-name x)))))
152                    *slot-options*))))
153   (eval
154    `(defclass hyperobject-esd (standard-effective-slot-definition)
155       (,@(mapcar #'(lambda (x)
156                      `(,(intern (symbol-name x))
157                         :initarg
158                         ,(intern (symbol-name x) (symbol-name :keyword))
159                         :initform nil
160                         :accessor
161                         ,(intern (concatenate 'string
162                                               (symbol-name :esd-)
163                                               (symbol-name x)))))
164                  (append *slot-options* *slot-options-no-initarg*)))))
165   ) ;; eval-when
166
167 (defun intern-in-keyword (obj)
168   (cond
169     ((null obj)
170      nil)
171     ((eq t obj)
172      t)
173     ((atom obj)
174      (intern (symbol-name obj) (find-package 'keyword)))
175     ((consp obj)
176      (cons (intern-in-keyword (car obj) ) (intern-in-keyword (cdr obj))))
177     (t
178      obj)))
179
180 (defmethod compute-effective-slot-definition :around ((cl hyperobject-class)
181                                                       #+(or allegro lispworks) name
182                                                       dsds)
183   #+allergo (declare (ignore name))
184   (let* ((dsd (car dsds))
185          (ho-type (intern-in-keyword (slot-value dsd 'type)))
186          (sql-type (ho-type-to-sql-type ho-type))
187          (length (when (consp ho-type) (cadr ho-type))))
188   #+allergo (declare (ignore name))
189     (setf (slot-value dsd 'ho-type) ho-type)
190     (setf (slot-value dsd 'sql-type) sql-type)
191     (setf (slot-value dsd 'type) (ho-type-to-lisp-type ho-type))
192     (let ((ia (compute-effective-slot-definition-initargs
193                cl #+lispworks name dsds)))
194       (apply
195        #'make-instance 'hyperobject-esd 
196        :ho-type ho-type
197        :sql-type sql-type
198        :length length
199        :print-formatter (slot-value dsd 'print-formatter)
200        :subobject (slot-value dsd 'subobject)
201        :hyperlink (slot-value dsd 'hyperlink)
202        :hyperlink-parameters (slot-value dsd 'hyperlink-parameters)
203        :description (slot-value dsd 'description)
204        :user-name (slot-value dsd 'user-name)
205        :index (slot-value dsd 'index)
206        ia))))
207
208 (defun ho-type-to-lisp-type (ho-type)
209   (when (consp ho-type)
210     (setq ho-type (car ho-type)))
211   (check-type ho-type symbol)
212   (case ho-type
213     ((or :string :cdata :varchar :char)
214      'string)
215     (:character
216      'character)
217     (:fixnum
218      'fixnum)
219     (:boolean
220      'boolean)
221     (:integer
222      'integer)
223     ((or :float :single-float)
224      'single-float)
225     (:double-float
226      'double-float)
227     (nil
228      t)
229     (otherwise
230      ho-type)))
231
232 (defun ho-type-to-sql-type (ho-type)
233   (when (consp ho-type)
234     (setq ho-type (car ho-type)))
235   (check-type ho-type symbol)
236   (case ho-type
237     ((or :string :cdata)
238      'string)
239     (:fixnum
240      'integer)
241     (:boolean
242      'boolean)
243     (:integer
244      'integer)
245     ((or :float :single-float)
246      'single-float)
247     (:double-float
248      'double-float)
249     (nil
250      t)
251     (otherwise
252      ho-type)))
253
254
255 ;;;; Class initialization function
256
257 ;; defines a slot-unbound method for class and slot-name, fills
258 ;; the slot by calling reader function with the slot values of
259 ;; the instance's reader-keys
260 (defmacro def-lazy-reader (class slot-name reader &rest reader-keys)
261   (let* ((the-slot-name (gensym))
262          (the-class (gensym))
263          (the-instance (gensym))
264          (keys '()))
265     (dolist (key reader-keys)
266       (push (list 'slot-value the-instance (list 'quote key)) keys))
267     (setq keys (nreverse keys))
268     `(defmethod slot-unbound (,the-class (,the-instance ,class)
269                                          (,the-slot-name (eql ',slot-name)))
270        (declare (ignore ,the-class))
271        (setf (slot-value ,the-instance ,the-slot-name)
272            (,reader ,@keys)))))
273
274 (defun finalize-subobjects (cl)
275   "Process class subobjects slot"
276   (setf (subobjects cl)
277     (let ((subobjects '()))
278       (dolist (slot (class-slots cl))
279         (let-when (subobj-def (esd-subobject slot))
280           (let ((subobject (make-instance 'subobject
281                                           :name-class (class-name cl)
282                                           :name-slot (slot-definition-name slot)
283                                           :lookup (if (atom subobj-def)
284                                                       subobj-def
285                                                       (car subobj-def))
286                                           :lookup-keys (if (atom subobj-def)
287                                                            nil
288                                                            (cdr subobj-def)))))
289             (unless (eq (lookup subobject) t)
290               (eval `(def-lazy-reader ,(name-class subobject)
291                          ,(name-slot subobject) ,(lookup subobject)
292                          ,@(lookup-keys subobject))))
293             (push subobject subobjects))))
294       subobjects)))
295
296 (defun finalize-documentation (cl)
297   "Calculate class documentation slot"
298   (awhen (slot-value cl 'user-name)
299          (setf (slot-value cl 'user-name)
300                (etypecase (slot-value cl 'user-name)
301                    (cons (car it))
302                    ((or string symbol) it))))
303   (awhen (slot-value cl 'description)
304          (setf (slot-value cl 'description)
305                (etypecase (slot-value cl 'description)
306                    (cons (car it))
307                    ((or string symbol) it))))
308
309   (let ((*print-circle* nil))
310     (setf (documentation (class-name cl) 'class)
311       (format nil "Hyperobject~A~A~A~A"
312               (aif (user-name cl)
313                    (format nil ": ~A" it ""))
314               (aif (description cl)
315                    (format nil "~%Class description: ~A" it) "")
316               (aif (subobjects cl)
317                    (format nil "~%Subobjects:~{ ~A~}" (mapcar #'name-slot it)) "")
318               (aif (default-print-slots cl)
319                    (format nil "~%Default print slots:~{ ~A~}" it) "")
320               ))))
321
322 (defun finalize-hyperlinks (cl)
323   (let ((hyperlinks '()))
324     (dolist (esd (class-slots cl))
325       (awhen (slot-value esd 'hyperlink)
326         (push
327          (make-instance 'hyperlink
328                         :name (slot-definition-name esd)
329                         :lookup it
330                         :link-parameters (slot-value esd 'hyperlink-parameters))
331          hyperlinks)))
332     (setf (slot-value cl 'hyperlinks) hyperlinks)))
333
334 (defun init-hyperobject-class (cl)
335   "Initialize a hyperobject class. Calculates all class slots"
336   (finalize-subobjects cl)
337   (finalize-views cl)
338   (finalize-hyperlinks cl)
339   (finalize-sql cl)
340   (finalize-documentation cl))
341
342
343 ;;;; *************************************************************************
344 ;;;;  Metaclass Slot Accessors
345 ;;;; *************************************************************************
346
347 (defun find-slot-by-name (cl name)
348   (find name (class-slots cl) :key #'slot-definition-name))
349
350 (defun hyperobject-class-user-name (obj)
351   (awhen (user-name (class-of obj))
352          (if (consp it)
353              (car it)
354              it)))
355
356 (defun hyperobject-class-subobjects (obj)
357   (subobjects (class-of obj)))
358
359 (defun hyperobject-class-hyperlinks (obj)
360   (hyperlinks (class-of obj)))
361
362 (defun hyperobject-class-fields (obj)
363   (class-slots (class-of obj)))
364