r3596: *** 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.9 2002/12/09 19:37:54 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           :documentation "User name for class")
31    (print-slots :initarg :print-slots :type list :initform nil
32                 :documentation "List of slots to print")
33    (description :initarg :description :initform nil
34                 :documentation "Class description")
35    (version :initarg :version :initform nil
36                 :documentation "Version number for class")
37    (sql-name :initarg :table-name :initform nil)
38
39    ;;; The remainder of these fields are calculated one time
40    ;;; in finalize-inheritence.
41    
42    (subobjects :initform nil :documentation
43                "List of fields that contain a list of subobjects objects.")
44    (hyperlinks :type list :initform nil :documentation 
45                "List of fields that have hyperlinks")
46    (class-id :type integer :initform nil :documentation
47              "Unique ID for the class")
48    
49    (create-table-cmd :initform nil :reader create-table-cmd)
50    (create-indices-cmds :initform nil :reader create-index-cmds)
51    (drop-table-cmd :initform nil :reader drop-table-cmd)
52
53    (value-func :initform nil :type function)
54    (xmlvalue-func :initform nil :type function)
55    (fmtstr-text :initform nil :type string)
56    (fmtstr-html :initform nil :type string)
57    (fmtstr-xml :initform nil :type string)
58    (fmtstr-text-labels :initform nil :type string)
59    (fmtstr-html-labels :initform nil :type string)
60    (fmtstr-xml-labels :initform nil :type string)
61    (fmtstr-html-ref :initform nil :type string)
62    (fmtstr-xml-ref :initform nil :type string)
63    (fmtstr-html-ref-labels :initform nil :type string)
64    (fmtstr-xml-ref-labels :initform nil :type string)
65    )
66   (:documentation "Metaclass for Markup Language classes."))
67
68 (defclass subobject ()
69   ((name-class :type symbol :initform nil :initarg :name-class :reader name-class)
70    (name-slot :type symbol :initform nil :initarg :name-slot :reader name-slot)
71    (lookup :type symbol :initform nil :initarg :lookup :reader lookup)
72    (lookup-keys :type list :initform nil :initarg :lookup-keys
73                 :reader lookup-keys))
74   (:documentation "Contains subobject information"))
75
76
77 (defmethod print-object ((obj subobject) (s stream))
78   (print-unreadable-object (obj s :type t :identity t)
79     (format s "~S" (name obj))))
80
81 (defclass hyperlink ()
82   ((name :type symbol :initform nil :initarg :name :reader name)
83    (lookup :type function :initform nil :initarg :lookup :reader lookup)
84    (link-parameters :type list :initform nil :initarg :link-parameters
85                     :reader link-parameters)))
86
87 (defmethod print-object ((obj hyperlink) (s stream))
88   (print-unreadable-object (obj s :type t :identity t)
89     (format s "~S" (name obj))))
90
91 #+(or cmu scl sbcl)
92 (defmethod validate-superclass ((class hyperobject-class) (superclass standard-class))
93   t)
94
95 (defmethod finalize-inheritance :after ((cl hyperobject-class))
96   (init-hyperobject-class cl))
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                    *slot-options*))))
149   (eval
150    `(defclass hyperobject-esd (standard-effective-slot-definition)
151       (,@(mapcar #'(lambda (x)
152                      `(,(intern (symbol-name x))
153                         :initarg
154                         ,(intern (symbol-name x) (symbol-name :keyword))
155                         :initform nil))
156                   (append *slot-options* *slot-options-no-initarg*)))))
157   ) ;; eval-when
158
159 (defun intern-in-keyword (obj)
160   (cond
161     ((null obj)
162      nil)
163     ((eq t obj)
164      t)
165     ((atom obj)
166      (intern (symbol-name obj) (find-package 'keyword)))
167     ((consp obj)
168      (cons (intern-in-keyword (car obj) ) (intern-in-keyword (cdr obj))))
169     (t
170      obj)))
171
172 (defmethod compute-effective-slot-definition :around
173     ((cl hyperobject-class) #+(or allegro lispworks) name dsds)
174   #+allergo (declare (ignore name))
175   (let* ((dsd (car dsds))
176          (ho-type (intern-in-keyword (slot-value dsd 'type)))
177          (sql-type (ho-type-to-sql-type ho-type))
178          (length (when (consp ho-type) (cadr ho-type))))
179     (setf (slot-value dsd 'ho-type) ho-type)
180     (setf (slot-value dsd 'sql-type) sql-type)
181     (setf (slot-value dsd 'type) (ho-type-to-lisp-type ho-type))
182     (let ((ia (compute-effective-slot-definition-initargs
183                cl #+lispworks name dsds)))
184       (apply
185        #'make-instance 'hyperobject-esd 
186        :ho-type ho-type
187        :sql-type sql-type
188        :length length
189        :print-formatter (slot-value dsd 'print-formatter)
190        :subobject (slot-value dsd 'subobject)
191        :hyperlink (slot-value dsd 'hyperlink)
192        :hyperlink-parameters (slot-value dsd 'hyperlink-parameters)
193        :description (slot-value dsd 'description)
194        :user-name (slot-value dsd 'user-name)
195        :index (slot-value dsd 'index)
196        ia))))
197
198 (defun ho-type-to-lisp-type (ho-type)
199   (when (consp ho-type)
200     (setq ho-type (car ho-type)))
201   (check-type ho-type symbol)
202   (case ho-type
203     ((or :string :cdata :varchar :char)
204      'string)
205     (:character
206      'character)
207     (:fixnum
208      'fixnum)
209     (:boolean
210      'boolean)
211     (:integer
212      'integer)
213     ((or :float :single-float)
214      'single-float)
215     (:double-float
216      'double-float)
217     (nil
218      t)
219     (otherwise
220      ho-type)))
221
222 (defun ho-type-to-sql-type (ho-type)
223   (when (consp ho-type)
224     (setq ho-type (car ho-type)))
225   (check-type ho-type symbol)
226   (case ho-type
227     ((or :string :cdata)
228      'string)
229     (:fixnum
230      'integer)
231     (:boolean
232      'boolean)
233     (:integer
234      'integer)
235     ((or :float :single-float)
236      'single-float)
237     (:double-float
238      'double-float)
239     (nil
240      t)
241     (otherwise
242      ho-type)))
243
244
245 ;;;; Class initialization function
246
247 ;; defines a slot-unbound method for class and slot-name, fills
248 ;; the slot by calling reader function with the slot values of
249 ;; the instance's reader-keys
250 (defmacro def-lazy-reader (class slot-name reader &rest reader-keys)
251   (let* ((the-slot-name (gensym))
252          (the-class (gensym))
253          (the-instance (gensym))
254          (keys '()))
255     (dolist (key reader-keys)
256       (push (list 'slot-value the-instance (list 'quote key)) keys))
257     (setq keys (nreverse keys))
258     `(defmethod slot-unbound (,the-class (,the-instance ,class)
259                                          (,the-slot-name (eql ',slot-name)))
260        (declare (ignore ,the-class))
261        (setf (slot-value ,the-instance ,the-slot-name)
262            (,reader ,@keys)))))
263
264 (defun finalize-subobjects (cl)
265   "Process class subobjects slot"
266   (setf (slot-value cl 'subobjects)
267     (let ((subobjects '()))
268       (dolist (slot (class-slots cl))
269         (let-when (subobj-def (slot-value slot 'subobject))
270           (let ((subobject (make-instance 'subobject
271                                           :name-class (class-name cl)
272                                           :name-slot (slot-definition-name slot)
273                                           :lookup (if (atom subobj-def)
274                                                       subobj-def
275                                                       (car subobj-def))
276                                           :lookup-keys (if (atom subobj-def)
277                                                            nil
278                                                            (cdr subobj-def)))))
279             (unless (eq (lookup subobject) t)
280               (eval `(def-lazy-reader ,(name-class subobject)
281                          ,(name-slot subobject) ,(lookup subobject)
282                          ,@(lookup-keys subobject))))
283             (push subobject subobjects))))
284       subobjects)))
285
286 (defun finalize-documentation (cl)
287   "Calculate class documentation slot"
288   (awhen (slot-value cl 'user-name)
289          (setf (slot-value cl 'user-name)
290                (etypecase (slot-value cl 'user-name)
291                    (cons (car it))
292                    ((or string symbol) it))))
293   (awhen (slot-value cl 'description)
294          (setf (slot-value cl 'description)
295                (etypecase (slot-value cl 'description)
296                    (cons (car it))
297                    ((or string symbol) it))))
298
299   (let ((*print-circle* nil))
300     (setf (documentation (class-name cl) 'class)
301       (format nil "Hyperobject~A~A~A~A"
302               (aif (slot-value cl 'user-name)
303                    (format nil ": ~A" it ""))
304               (aif (slot-value cl 'description)
305                    (format nil "~%Class description: ~A" it) "")
306               (aif (slot-value cl 'subobjects)
307                    (format nil "~%Subobjects:~{ ~A~}" (mapcar #'name-slot it)) "")
308               (aif (slot-value cl 'print-slots)
309                    (format nil "~%Print-slots:~{ ~A~}" it) "")
310               ))))
311
312 (defun init-hyperobject-class (cl)
313   "Initialize a hyperobject class. Calculates all class slots"
314   (finalize-subobjects cl)
315   (finalize-views cl)
316   (finalize-hyperlinks cl)
317   (finalize-sql cl)
318   (finalize-documentation cl))
319
320
321 ;;;; *************************************************************************
322 ;;;;  Metaclass Slot Accessors
323 ;;;; *************************************************************************
324
325 (defun find-slot-by-name (cl name)
326   (find name (class-slots cl) :key #'slot-definition-name))
327
328 (defun hyperobject-class-fmtstr-text (obj)
329   (slot-value (class-of obj) 'fmtstr-text))
330
331 (defun hyperobject-class-fmtstr-html (obj)
332   (slot-value (class-of obj) 'fmtstr-html))
333
334 (defun hyperobject-class-fmtstr-xml (obj)
335   (slot-value (class-of obj) 'fmtstr-xml))
336
337 (defun hyperobject-class-fmtstr-text-labels (obj)
338   (slot-value (class-of obj) 'fmtstr-text-labels))
339
340 (defun hyperobject-class-fmtstr-html-labels (obj)
341   (slot-value (class-of obj) 'fmtstr-html-labels))
342
343 (defun hyperobject-class-fmtstr-xml-labels (obj)
344   (slot-value (class-of obj) 'fmtstr-xml-labels))
345
346 (defun hyperobject-class-value-func (obj)
347   (slot-value (class-of obj) 'value-func))
348
349 (defun hyperobject-class-xmlvalue-func (obj)
350   (slot-value (class-of obj) 'xmlvalue-func))
351
352 (defun hyperobject-class-user-name (obj)
353   (awhen (slot-value (class-of obj) 'user-name)
354          (if (consp it)
355              (car it)
356              it)))
357
358 (defun hyperobject-class-subobjects (obj)
359   (slot-value (class-of obj) 'subobjects))
360
361 (defun hyperobject-class-hyperlinks (obj)
362   (slot-value (class-of obj) 'hyperlinks))
363
364 (defun hyperobject-class-fields (obj)
365   (class-slots (class-of obj)))
366
367 (defun hyperobject-class-print-slots (obj)
368   (slot-value (class-of obj) 'print-slots))
369
370 (defun hyperobject-class-fmtstr-html-ref (obj)
371   (slot-value (class-of obj) 'fmtstr-html-ref))
372
373 (defun hyperobject-class-fmtstr-xml-ref (obj)
374   (slot-value (class-of obj) 'fmtstr-xml-ref))
375
376 (defun hyperobject-class-fmtstr-html-ref-labels (obj)
377   (slot-value (class-of obj) 'fmtstr-html-ref-labels))
378
379 (defun hyperobject-class-fmtstr-xml-ref-labels (obj)
380   (slot-value (class-of obj) 'fmtstr-xml-ref-labels))
381