r3630: *** empty log message ***
[hyperobject.git] / mop.lisp
index 151baf285a934c02a93cda5e6a9ef751f787c58d..496bc56c3ae7977205f3a3d34e24051b9701f2f7 100644 (file)
--- a/mop.lisp
+++ b/mop.lisp
@@ -2,8 +2,8 @@
 ;;;; *************************************************************************
 ;;;; FILE IDENTIFICATION
 ;;;;
-;;;; Name:          hyperobject.lisp
-;;;; Purpose:       Hyper Object Metaclass
+;;;; Name:          mop.lisp
+;;;; Purpose:       Metaobject Protocol Interface
 ;;;; Programmer:    Kevin M. Rosenberg
 ;;;; Date Started:  Apr 2000
 ;;;;
@@ -11,7 +11,7 @@
 ;;;; in Text, HTML, and XML formats. This includes hyperlinking
 ;;;; capability and sub-objects.
 ;;;;
-;;;; $Id: mop.lisp,v 1.1 2002/11/29 04:07:52 kevin Exp $
+;;;; $Id: mop.lisp,v 1.16 2002/12/14 21:52:48 kevin Exp $
 ;;;;
 ;;;; This file is Copyright (c) 2000-2002 by Kevin M. Rosenberg
 ;;;;
 (in-package :hyperobject)
 
 (eval-when (:compile-toplevel :execute)
-  (declaim (optimize (speed 3) (safety 1) (compilation-speed 0) (debug 3))))
-
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (shadowing-import
-   #+allegro
-   `(mop::class-slots mop::slot-definition-name mop:finalize-inheritance
-     mop::standard-direct-slot-definition mop::standard-effective-slot-definition
-     mop:direct-slot-definition-class mop:compute-effective-slot-definition
-     excl::compute-effective-slot-definition-initargs)
-   #+lispworks
-   `(clos:class-slots clos::slot-definition-name clos:finalize-inheritance
-     clos::standard-direct-slot-definition clos::standard-effective-slot-definition
-     clos:direct-slot-definition-class clos:compute-effective-slot-definition
-     clos::compute-effective-slot-definition-initargs)
-   #+sbcl 
-   `(sb-pcl:class-of sb-pcl:class-name sb-pcl:class-slots sb-pcl::standard-class
-     sb-pcl::slot-definition-name sb-pcl:finalize-inheritance
-     sb-pcl::standard-direct-slot-definition
-     sb-pcl::standard-effective-slot-definition sb-pcl::validate-superclass
-     sb-pcl:direct-slot-definition-class sb-pcl:compute-effective-slot-definition
-     sb-pcl::compute-effective-slot-definition-initargs)
-   #+cmu
-   `(pcl:class-of  pcl:class-name pcl:class-slots pcl::standard-class
-     pcl::slot-definition-name pcl:finalize-inheritance
-     pcl::standard-direct-slot-definition pcl::standard-effective-slot-definition
-     pcl::validate-superclass pcl:direct-slot-definition-class
-     pcl:compute-effective-slot-definition
-     pcl::compute-effective-slot-definition-initargs)
-   #+scl
-   `(class-of  class-name clos:class-slots clos::standard-class
-     clos::slot-definition-name clos:finalize-inheritance
-     clos::standard-direct-slot-definition clos::standard-effective-slot-definition
-     clos::validate-superclass clos:direct-slot-definition-class
-     clos:compute-effective-slot-definition
-     clos::compute-effective-slot-definition-initargs)
-   
-   :hyperobject))
-
+  (declaim (optimize (speed 2) (safety 2) (compilation-speed 0) (debug 2))))
 
 ;; Main class
 
 (defclass hyperobject-class (standard-class)
   ( ;; slots initialized in defclass
-   (title :initarg :title :type string :initform nil
-         :documentation "Print Title for class")
-   (print-slots :initarg :print-slots :type list :initform nil
-               :documentation "List of slots to print")
+   (user-name :initarg :user-name :type string :initform nil
+             :accessor user-name
+             :documentation "User name for class")
+   (default-print-slots :initarg :default-print-slots :type list :initform nil
+                       :accessor default-print-slots
+                       :documentation "Defaults slots for a view")
    (description :initarg :description :initform nil
+               :accessor description
                :documentation "Class description")
    (version :initarg :version :initform nil
-               :documentation "Version number for class")
+           :accessor version
+           :documentation "Version number for class")
+   (sql-name :initarg :sql-name :initform nil)
 
    ;;; The remainder of these fields are calculated one time
    ;;; in finalize-inheritence.
    
-   (subobjects :initform nil :documentation
+   (subobjects :initform nil :accessor subobjects
+              :documentation
               "List of fields that contain a list of subobjects objects.")
-   (references :type list :initform nil :documentation 
-              "List of fields that have references")
-   (class-id :type integer :initform nil :documentation
-            "Unique ID for the class")
-   
-   (value-func :initform nil :type function)
-   (xmlvalue-func :initform nil :type function)
-   (fmtstr-text :initform nil :type string)
-   (fmtstr-html :initform nil :type string)
-   (fmtstr-xml :initform nil :type string)
-   (fmtstr-text-labels :initform nil :type string)
-   (fmtstr-html-labels :initform nil :type string)
-   (fmtstr-xml-labels :initform nil :type string)
-   (fmtstr-html-ref :initform nil :type string)
-   (fmtstr-xml-ref :initform nil :type string)
-   (fmtstr-html-ref-labels :initform nil :type string)
-   (fmtstr-xml-ref-labels :initform nil :type string)
+   (hyperlinks :type list :initform nil :accessor hyperlinks
+              :documentation "List of fields that have hyperlinks")
+   (direct-rules :type list :initform nil :initarg :direct-rules
+                :accessor direct-rules
+                :documentation "List of rules to fire on slot changes.")
+   (class-id :type integer :initform nil
+            :accessor class-id
+            :documentation "Unique ID for the class")
+   (default-view :initform nil :initarg :default-view :accessor default-view
+                :documentation "The default view for a class")
+
+   ;; SQL commands
+   (create-table-cmd :initform nil :reader create-table-cmd)
+   (create-indices-cmds :initform nil :reader create-index-cmds)
+   (drop-table-cmd :initform nil :reader drop-table-cmd)
+
+   (views :type list :initform nil :initarg :views :accessor views
+         :documentation "List of views")
+   (rules :type list :initform nil :initarg :rules :accessor rules
+         :documentation "List of rules")
    )
   (:documentation "Metaclass for Markup Language classes."))
 
 (defclass subobject ()
-  ((name :type symbol :initform nil :initarg :name :reader name)
-   (reader :type function :initform nil :initarg :reader :reader reader)))
+  ((name-class :type symbol :initform nil :initarg :name-class :reader name-class)
+   (name-slot :type symbol :initform nil :initarg :name-slot :reader name-slot)
+   (lookup :type symbol :initform nil :initarg :lookup :reader lookup)
+   (lookup-keys :type list :initform nil :initarg :lookup-keys
+               :reader lookup-keys))
+  (:documentation "Contains subobject information"))
+
 
 (defmethod print-object ((obj subobject) (s stream))
   (print-unreadable-object (obj s :type t :identity t)
     (format s "~S" (name obj))))
 
-(defclass reference ()
+(defclass hyperlink ()
   ((name :type symbol :initform nil :initarg :name :reader name)
    (lookup :type function :initform nil :initarg :lookup :reader lookup)
    (link-parameters :type list :initform nil :initarg :link-parameters
                    :reader link-parameters)))
 
-(defmethod print-object ((obj reference) (s stream))
+(defmethod print-object ((obj hyperlink) (s stream))
   (print-unreadable-object (obj s :type t :identity t)
     (format s "~S" (name obj))))
 
   t)
 
 (defmethod finalize-inheritance :after ((cl hyperobject-class))
-  (init-hyperobject-class cl))
+  (init-hyperobject-class cl)
+  )
 
 ;; Slot definitions
 (defmethod direct-slot-definition-class ((cl hyperobject-class) 
     (declare (ignore slot-name))
     )
   
-  (defparameter *class-options*
-    '(:title :print-slots :description :version :sql-name)
-    "List of class options for hyperobjects.")
-  (defparameter *slot-options*
-    '(:print-formatter :subobject :reference :description :unique :sql-name)
-    "List of slot options that can appear as an initarg")
-  (defparameter *slot-options-no-initarg*
-    '(:ho-type)
-    "List of slot options that do not have an initarg")
-
   (dolist (option *class-options*)
     (eval `(process-class-option ,option)))
   (dolist (option *slot-options*)
                       `(,(intern (symbol-name x))
                          :initarg
                          ,(intern (symbol-name x) (symbol-name :keyword))
-                         :initform nil))
+                         :initform nil
+                         :accessor
+                         ,(intern (concatenate 'string
+                                               (symbol-name :dsd-)
+                                               (symbol-name x)))))
                   *slot-options*))))
   (eval
    `(defclass hyperobject-esd (standard-effective-slot-definition)
                     `(,(intern (symbol-name x))
                        :initarg
                        ,(intern (symbol-name x) (symbol-name :keyword))
-                       :initform nil))
-                 (append *slot-options* *slot-options-no-initarg*)))))
+                       :initform nil
+                       :accessor
+                       ,(intern (concatenate 'string
+                                             (symbol-name :esd-)
+                                             (symbol-name x)))))
+                (append *slot-options* *slot-options-no-initarg*)))))
   ) ;; eval-when
-  
-(defmethod compute-effective-slot-definition :around
-    ((cl hyperobject-class) #+(or allegro lispworks) name dsds)
+
+(defun intern-in-keyword (obj)
+  (cond
+    ((null obj)
+     nil)
+    ((eq t obj)
+     t)
+    ((atom obj)
+     (intern (symbol-name obj) (find-package 'keyword)))
+    ((consp obj)
+     (cons (intern-in-keyword (car obj) ) (intern-in-keyword (cdr obj))))
+    (t
+     obj)))
+
+(defun canonicalize-value-type (vt)
+  (typecase vt
+    (atom
+     (ensure-keyword vt))
+    (cons
+     (cons (ensure-keyword (car vt)) (cdr vt)))
+    (t
+     t)))
+
+(defmethod compute-effective-slot-definition :around ((cl hyperobject-class)
+                                                     #+(or allegro lispworks) name
+                                                     dsds)
   #+allergo (declare (ignore name))
   (let* ((dsd (car dsds))
-        (ho-type (slot-value dsd 'type)))
-    (setf (slot-value dsd 'ho-type) ho-type)
-    (setf (slot-value dsd 'type) (convert-ho-type ho-type))
-    (let ((ia (compute-effective-slot-definition-initargs
-              cl #+lispworks name dsds)))
-      (apply
-       #'make-instance 'hyperobject-esd 
-       :ho-type ho-type
-       :print-formatter (slot-value dsd 'print-formatter)
-       :subobject (slot-value dsd 'subobject)
-       :reference (slot-value dsd 'reference)
-       :description (slot-value dsd 'description)
-       ia)))
-  )
-
-(defun convert-ho-type (ho-type)
-  (check-type ho-type symbol)
-  (case (intern (symbol-name ho-type) (symbol-name :keyword))
-    (:string
+        (value-type (canonicalize-value-type (slot-value dsd 'value-type))))
+    (multiple-value-bind (sql-type length) (value-type-to-sql-type value-type)
+      (setf (slot-value dsd 'sql-type) sql-type)
+      (setf (slot-value dsd 'type) (value-type-to-lisp-type value-type))
+      (let ((ia (compute-effective-slot-definition-initargs
+                cl #+lispworks name dsds)))
+       (apply
+        #'make-instance 'hyperobject-esd 
+        :value-type value-type
+        :sql-type sql-type
+        :length length
+        :print-formatter (slot-value dsd 'print-formatter)
+        :subobject (slot-value dsd 'subobject)
+        :hyperlink (slot-value dsd 'hyperlink)
+        :hyperlink-parameters (slot-value dsd 'hyperlink-parameters)
+        :description (slot-value dsd 'description)
+        :user-name (slot-value dsd 'user-name)
+        :index (slot-value dsd 'index)
+        :value-constraint (slot-value dsd 'value-constraint)
+        :null-allowed (slot-value dsd 'null-allowed)
+        ia)))))
+  
+(defun value-type-to-lisp-type (value-type)
+  (case (if (atom value-type)
+           value-type
+           (car value-type))
+    ((:string :cdata :varchar :char)
      'string)
+    (:character
+     'character)
     (:fixnum
      'fixnum)
     (:boolean
      'boolean)
     (:integer
      'integer)
-    (:cdata
-     'string)
-    (:float
-     'float)
-    (:nil
-     t)
+    ((:float :single-float)
+     'single-float)
+    (:double-float
+     'double-float)
     (otherwise
-     ho-type)))
+     t)))
+
+(defun value-type-to-sql-type (value-type)
+  "Return two values, the sql type and field length."
+  (let ((type (if (atom value-type)
+                 value-type
+                 (car value-type)))
+       (length (when (consp value-type)
+                 (cadr value-type))))
+    (values
+     (case type
+       ((:string :cdata)
+       :string)
+       ((:fixnum :integer)
+       :integer)
+       (:boolean
+       :boolean)
+       ((:float :single-float)
+       :single-float)
+       (:double-float
+       :double-float)
+       (otherwise
+       :text))
+     length)))
 
 ;;;; Class initialization function
 
-(defun find-slot-by-name (cl name)
-  (find name (class-slots cl) :key #'slot-definition-name))
-
-
-(defun process-subobjects (cl)
+;; defines a slot-unbound method for class and slot-name, fills
+;; the slot by calling reader function with the slot values of
+;; the instance's reader-keys
+(defmacro def-lazy-reader (class slot-name reader &rest reader-keys)
+  (let* ((the-slot-name (gensym))
+        (the-class (gensym))
+        (the-instance (gensym))
+        (keys '()))
+    (dolist (key reader-keys)
+      (push (list 'slot-value the-instance (list 'quote key)) keys))
+    (setq keys (nreverse keys))
+    `(defmethod slot-unbound (,the-class (,the-instance ,class)
+                                        (,the-slot-name (eql ',slot-name)))
+       (declare (ignore ,the-class))
+       (setf (slot-value ,the-instance ,the-slot-name)
+          (,reader ,@keys)))))
+
+(defun finalize-subobjects (cl)
   "Process class subobjects slot"
-  (setf (slot-value cl 'subobjects)
+  (setf (subobjects cl)
     (let ((subobjects '()))
       (dolist (slot (class-slots cl))
-       (when (slot-value slot 'subobject)
-         (push (make-instance 'subobject :name (slot-definition-name slot)
-                              :reader (if (eq t (slot-value slot 'subobject))
-                                          (slot-definition-name slot)
-                                        (slot-value slot 'subobject)))
-               subobjects)))
+       (let-when (subobj-def (esd-subobject slot))
+          (let ((subobject (make-instance 'subobject
+                                         :name-class (class-name cl)
+                                         :name-slot (slot-definition-name slot)
+                                         :lookup (if (atom subobj-def)
+                                                     subobj-def
+                                                     (car subobj-def))
+                                         :lookup-keys (if (atom subobj-def)
+                                                          nil
+                                                          (cdr subobj-def)))))
+           (unless (eq (lookup subobject) t)
+             (eval `(def-lazy-reader ,(name-class subobject)
+                        ,(name-slot subobject) ,(lookup subobject)
+                        ,@(lookup-keys subobject))))
+           (push subobject subobjects))))
       subobjects)))
 
-(defun process-documentation (cl)
+(defun finalize-documentation (cl)
   "Calculate class documentation slot"
-  (awhen (slot-value cl 'title)
-        (setf (slot-value cl 'title) (car it)))
+  (awhen (slot-value cl 'user-name)
+        (setf (slot-value cl 'user-name)
+              (etypecase (slot-value cl 'user-name)
+                  (cons (car it))
+                  ((or string symbol) it))))
   (awhen (slot-value cl 'description)
-        (setf (slot-value cl 'description) (car it)))
-  
+        (setf (slot-value cl 'description)
+              (etypecase (slot-value cl 'description)
+                  (cons (car it))
+                  ((or string symbol) it))))
+
   (let ((*print-circle* nil))
     (setf (documentation (class-name cl) 'class)
       (format nil "Hyperobject~A~A~A~A"
-             (aif (slot-value cl 'title)
+             (aif (user-name cl)
                   (format nil ": ~A" it ""))
-             (aif (slot-value cl 'description)
+             (aif (description cl)
                   (format nil "~%Class description: ~A" it) "")
-             (aif (slot-value cl 'subobjects)
-                  (format nil "~%Subobjects:~{ ~A~}" (mapcar #'name it)) "")
-             (aif (slot-value cl 'print-slots)
-                  (format nil "~%Print-slots:~{ ~A~}" it) "")
+             (aif (subobjects cl)
+                  (format nil "~%Subobjects:~{ ~A~}" (mapcar #'name-slot it)) "")
+             (aif (default-print-slots cl)
+                  (format nil "~%Default print slots:~{ ~A~}" it) "")
              ))))
 
-(defun process-views (cl)
-  "Calculate all view slots for a hyperobject class"
-  (let ((fmtstr-text "")
-       (fmtstr-html "")
-       (fmtstr-xml "")
-       (fmtstr-text-labels "")
-       (fmtstr-html-labels "")
-       (fmtstr-xml-labels "")
-       (fmtstr-html-ref "")
-       (fmtstr-xml-ref "")
-       (fmtstr-html-ref-labels "")
-       (fmtstr-xml-ref-labels "")
-       (first-field t)
-       (value-func '())
-       (xmlvalue-func '())
-       (classname (class-name cl))
-       (package (symbol-package (class-name cl)))
-       (references nil))
-    (declare (ignore classname))
-    (check-type (slot-value cl 'print-slots) list)
-    (dolist (slot-name (slot-value cl 'print-slots))
-      (let ((slot (find-slot-by-name cl slot-name)))
-       (unless slot
-         (error "Slot ~A is not found in class ~S" slot-name cl))
-       (let ((name (slot-definition-name slot))
-             (namestr (symbol-name (slot-definition-name slot)))
-             (namestr-lower (string-downcase (symbol-name (slot-definition-name slot))))
-             (type (slot-value slot 'ho-type))
-             (print-formatter (slot-value slot 'print-formatter))
-             (value-fmt "~a")
-             (plain-value-func nil)
-             html-str xml-str html-label-str xml-label-str)
-         
-         (when (or (eql type :integer) (eql type :fixnum))
-           (setq value-fmt "~d"))
-         
-         (when (eql type :boolean)
-           (setq value-fmt "~a"))
-         
-         (if first-field
-             (setq first-field nil)
-             (progn
-               (string-append fmtstr-text " ")
-               (string-append fmtstr-html " ")
-               (string-append fmtstr-xml " ")
-               (string-append fmtstr-text-labels " ")
-               (string-append fmtstr-html-labels " ")
-               (string-append fmtstr-xml-labels " ")
-               (string-append fmtstr-html-ref " ")
-               (string-append fmtstr-xml-ref " ")
-               (string-append fmtstr-html-ref-labels " ")
-               (string-append fmtstr-xml-ref-labels " ")))
-         
-         (setq html-str (concatenate 'string "<span class=\"" namestr-lower "\">" value-fmt "</span>"))
-         (setq xml-str (concatenate 'string "<" namestr-lower ">" value-fmt "</" namestr-lower ">"))
-         (setq html-label-str (concatenate 'string "<span class=\"label\">" namestr-lower "</span> <span class=\"" namestr-lower "\">" value-fmt "</span>"))
-         (setq xml-label-str (concatenate 'string "<label>" namestr-lower "</label> <" namestr-lower ">" value-fmt "</" namestr-lower ">"))
-         
-         (string-append fmtstr-text value-fmt)
-         (string-append fmtstr-html html-str)
-         (string-append fmtstr-xml xml-str)
-         (string-append fmtstr-text-labels namestr-lower " " value-fmt)
-         (string-append fmtstr-html-labels html-label-str)
-         (string-append fmtstr-xml-labels xml-label-str)
-         
-         (if (slot-value slot 'reference)
-             (progn
-               (string-append fmtstr-html-ref "<~~a>" value-fmt "</~~a>")
-               (string-append fmtstr-xml-ref "<~~a>" value-fmt "</~~a>")
-               (string-append fmtstr-html-ref-labels "<span class=\"label\">" namestr-lower "</span> <~~a>" value-fmt "</~~a>")
-               (string-append fmtstr-xml-ref-labels "<label>" namestr-lower "</label> <~~a>" value-fmt "</~~a>")
-               (push (make-instance 'reference :name name
-                                    :lookup (slot-value slot 'reference))
-                     references))
-             (progn
-               (string-append fmtstr-html-ref html-str)
-               (string-append fmtstr-xml-ref xml-str)
-               (string-append fmtstr-html-ref-labels html-label-str)
-               (string-append fmtstr-xml-ref-labels xml-label-str)))
-         
-         (if print-formatter
-             (setq plain-value-func 
-                   (list `(,print-formatter (slot-value x ',(intern namestr package)))))
-             (setq plain-value-func 
-                   (list `(slot-value x ',(intern namestr package)))))
-         (setq value-func (append value-func plain-value-func))
-         
-         (if (eql type :cdata)
-             (setq xmlvalue-func (append xmlvalue-func (list `(xml-cdata ,@plain-value-func))))
-             (setq xmlvalue-func (append xmlvalue-func plain-value-func)))
-         )))
-    
-    (setf (slot-value cl 'references) references)
-    
-    (if value-func
-       (setq value-func `(lambda (x) (values ,@value-func)))
-       (setq value-func `(lambda () (values))))
-    (setq value-func (compile nil (eval value-func)))
-    
-    (if xmlvalue-func
-       (setq xmlvalue-func `(lambda (x) (values ,@xmlvalue-func)))
-       (setq xmlvalue-func `(lambda () (values))))
-    (setq xmlvalue-func (compile nil (eval xmlvalue-func)))
-    
-    (setf (slot-value cl 'fmtstr-text) fmtstr-text)
-    (setf (slot-value cl 'fmtstr-html) fmtstr-html)
-    (setf (slot-value cl 'fmtstr-xml) fmtstr-xml)
-    (setf (slot-value cl 'fmtstr-text-labels) fmtstr-text-labels)
-    (setf (slot-value cl 'fmtstr-html-labels) fmtstr-html-labels)
-    (setf (slot-value cl 'fmtstr-xml-labels) fmtstr-xml-labels)
-    (setf (slot-value cl 'fmtstr-html-ref) fmtstr-html-ref)
-    (setf (slot-value cl 'fmtstr-xml-ref) fmtstr-xml-ref)
-    (setf (slot-value cl 'fmtstr-html-ref-labels) fmtstr-html-ref-labels)
-    (setf (slot-value cl 'fmtstr-xml-ref-labels) fmtstr-xml-ref-labels)
-    (setf (slot-value cl 'value-func) value-func)
-    (setf (slot-value cl 'xmlvalue-func) xmlvalue-func))
-  (values))
+(defun finalize-hyperlinks (cl)
+  (let ((hyperlinks '()))
+    (dolist (esd (class-slots cl))
+      (awhen (slot-value esd 'hyperlink)
+        (push
+        (make-instance 'hyperlink
+                       :name (slot-definition-name esd)
+                       :lookup it
+                       :link-parameters (slot-value esd 'hyperlink-parameters))
+        hyperlinks)))
+    (setf (slot-value cl 'hyperlinks) hyperlinks)))
 
 (defun init-hyperobject-class (cl)
   "Initialize a hyperobject class. Calculates all class slots"
-  (process-subobjects cl)
-  (process-views cl)
-  (process-documentation cl))
-
-(defun hyperobject-class-fmtstr-text (obj)
-  (slot-value (class-of obj) 'fmtstr-text))
-
-(defun hyperobject-class-fmtstr-html (obj)
-  (slot-value (class-of obj) 'fmtstr-html))
-
-(defun hyperobject-class-fmtstr-xml (obj)
-  (slot-value (class-of obj) 'fmtstr-xml))
-
-(defun hyperobject-class-fmtstr-text-labels (obj)
-  (slot-value (class-of obj) 'fmtstr-text-labels))
-
-(defun hyperobject-class-fmtstr-html-labels (obj)
-  (slot-value (class-of obj) 'fmtstr-html-labels))
+  (finalize-subobjects cl)
+  (finalize-views cl)
+  (finalize-hyperlinks cl)
+  (finalize-sql cl)
+  (finalize-rules cl)
+  (finalize-documentation cl))
 
-(defun hyperobject-class-fmtstr-xml-labels (obj)
-  (slot-value (class-of obj) 'fmtstr-xml-labels))
 
-(defun hyperobject-class-value-func (obj)
-  (slot-value (class-of obj) 'value-func))
+;;;; *************************************************************************
+;;;;  Metaclass Slot Accessors
+;;;; *************************************************************************
 
-(defun hyperobject-class-xmlvalue-func (obj)
-  (slot-value (class-of obj) 'xmlvalue-func))
+(defun find-slot-by-name (cl name)
+  (find name (class-slots cl) :key #'slot-definition-name))
 
-(eval-when (:compile-toplevel :load-toplevel :execute)
-  (defun hyperobject-class-title (obj)
-    (awhen (slot-value (class-of obj) 'title)
-          (if (consp it)
-              (car it)
-              it))))
+(defun hyperobject-class-user-name (obj)
+  (awhen (user-name (class-of obj))
+        (if (consp it)
+            (car it)
+            it)))
 
 (defun hyperobject-class-subobjects (obj)
-  (slot-value (class-of obj) 'subobjects))
+  (subobjects (class-of obj)))
 
-(defun hyperobject-class-references (obj)
-  (slot-value (class-of obj) 'references))
+(defun hyperobject-class-hyperlinks (obj)
+  (hyperlinks (class-of obj)))
 
 (defun hyperobject-class-fields (obj)
   (class-slots (class-of obj)))
 
-(defun hyperobject-class-fmtstr-html-ref (obj)
-  (slot-value (class-of obj) 'fmtstr-html-ref))
-
-(defun hyperobject-class-fmtstr-xml-ref (obj)
-  (slot-value (class-of obj) 'fmtstr-xml-ref))
-
-(defun hyperobject-class-fmtstr-html-ref-labels (obj)
-  (slot-value (class-of obj) 'fmtstr-html-ref-labels))
-
-(defun hyperobject-class-fmtstr-xml-ref-labels (obj)
-  (slot-value (class-of obj) 'fmtstr-xml-ref-labels))
-
+;;; Slot accessor and class rules
+
+(defclass rule ()
+  ((name :initarg :name :initform nil :accessor name)
+   (dependants :initarg :dependants :initform nil :accessor dependants)
+   (volatile :initarg :volatile :initform nil :accessor volatile)
+   (access-slots :initarg :access-slots :initform nil :accessor access-slots)
+   (source-code :initarg :source-code :initform nil :accessor source-code)
+   (func :initform nil :initarg :func :accessor func)))
+
+(defun compile-rule (source-code dependants volatile cl)
+  (let ((access (appendnew dependants volatile)))
+    (compile nil
+            (eval
+             `(lambda (obj)
+                (when (every #'(lambda (x) (slot-boundp obj x))
+                             (quote ,dependants))
+                  (with-slots ,access obj
+                    ,@source-code)))))))
+  
+(defun finalize-rules (cl)
+  (let* ((direct-rules (direct-rules cl))
+        (rules '()))
+    (dolist (rule direct-rules)
+      (destructuring-bind (name (&key dependants volatile) &rest source-code)
+         rule
+       (setf dependants (mklist dependants)
+             volatile (mklist volatile))
+       (push
+        (make-instance 'rule :name name :dependants dependants
+                       :volatile volatile :source-code source-code
+                       :access-slots (appendnew dependants volatile)
+                       :func (compile-rule
+                              source-code dependants volatile cl))
+        rules)))
+    (setf (rules cl) (nreverse rules))))
+
+
+(defun fire-class-rules (cl obj slot)
+  "Fire all class rules. Called after a slot is modified."
+  (let ((name (slot-definition-name slot)))
+    (dolist (rule (rules cl))
+      (when (find name (dependants rule))
+       (cmsg-c :debug "firing rule: ~W" (source-code rule))
+       (funcall (func rule) obj)))))
+
+
+#+ignore
+(defmethod (setf slot-value-using-class) 
+    :around (new-value (cl hyperobject-class) obj
+                      (slot standard-effective-slot-definition))
+    (call-next-method))
+
+(defmethod (setf slot-value-using-class) 
+    :around (new-value (cl hyperobject-class) obj
+                      (slot standard-effective-slot-definition))
+    #+ignore
+    (cmsg-c :verbose "Setf slot value: class: ~s, obj: ~s, slot: ~s, value: ~s" cl (class-of obj) slot new-value)
+    
+    (let ((func (esd-value-constraint slot)))
+      (cond
+       ((and func (not (funcall func new-value)))
+        (warn "Rejected change to value of slot ~a of object ~a"
+              (slot-definition-name slot) obj)
+        (slot-value obj (slot-definition-name slot)))
+       (t
+        (call-next-method)
+        (when (direct-rules cl)
+          (fire-class-rules cl obj slot))
+        new-value))))
+
+#+ignore
+(defmethod slot-value-using-class :around ((cl hyperobject-class) obj
+                                          (slot standard-effective-slot-definition))
+  (let ((value (call-next-method)))
+    (cmsg-c :verbose "slot value: class: ~s, obj: ~s, slot: ~s" cl (class-of obj) slot)
+    value))