/[cvs]/nfo/perl/libs/Class/Tangram.pm
ViewVC logotype

Diff of /nfo/perl/libs/Class/Tangram.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.1 by cvsjoko, Thu Oct 10 03:42:55 2002 UTC revision 1.4 by joko, Sat Nov 9 01:06:30 2002 UTC
# Line 1  Line 1 
1  package Class::Tangram;  package Class::Tangram;
2    
3  # Copyright (c) 2001 Sam Vilain. All rights reserved. This program is  # Copyright (c) 2001, 2002 Sam Vilain.  All right reserved.  This file
4  # free software; you can redistribute it and/or modify it under the  # is licensed under the terms of the Perl Artistic license.
 # same terms as Perl itself.  
   
 # Some modifications  
 # $Id$  
 # Copyright 2001 Micro Sharp Technologies, Inc., Vancouver, WA, USA  
 # Author: Karl M. Hegbloom <karlheg@microsharp.com>  
 # Perl Artistic Licence.  
5    
6  =head1 NAME  =head1 NAME
7    
# Line 17  objects from a Tangram-compatible object Line 10  objects from a Tangram-compatible object
10    
11  =head1 SYNOPSIS  =head1 SYNOPSIS
12    
13   package Orange;   package MyObject;
14    
15   use base qw(Class::Tangram);   use base qw(Class::Tangram);
  use vars qw($schema);  
  use Tangram::Ref;  
16    
17   # define the schema (ie, allowed attributes) of this object.  See the   our $fields => { int    => [ qw(foo bar) ],
18   # Tangram::Schema man page for more information on the syntax here.                    string => [ qw(baz quux) ] };
  $schema = {  
      table => "oranges",  
   
      fields => {  
          int => {  
              juiciness => undef,  
              segments => {  
                  # here is a new one - this code reference is called  
                  # when this attribute is set; it should die() on  
                  # error, as it is wrapped in an eval { } block  
                  check_func => sub {  
                      die "too many segments"  
                          if ($ {$_[0]} > 30);  
                  },  
                  # the default for this attribute.  
                  init_default => 7,  
              },  
          },  
          ref => {  
              grower => undef,  
          },  
      },  
  };  
  Class::Tangram::import_schema("Orange");  
19    
20   package Project;   package main;
  # here's where we build the individual object schemas into a  
  # Tangram::Schema object, which the Tangram::Storage class uses to  
  # know which tables and columns to find objects.  
  use Tangram::Schema;  
21    
22   my $dbschema = Tangram::Schema->new   my $object = MyObject->new(foo => 2, baz => "hello");
      ({ classes => [ 'Orange' => $Orange::schema ]});  
23    
24   sub schema { $dbschema };   print $object->baz();            # prints "hello"
25    
26   package main;   $object->set_quux("Something");
27    
28   # See Tangram::Relational for instructions on using "deploy" to   $object->set_foo("Something");   # dies - not an integer
  # create the database this connects to.  You only have to do this if  
  # you want to write the objects to a database.  
  use Tangram::Relational;  
  my ($dsn, $u, $p);  
  my $storage = Tangram::Relational->connect(Project->schema,  
                                             $dsn, $u, $p);  
29    
30   # OK  =head1 DESCRIPTION
  my $orange = Orange->new(juiciness => 8);  
  my $juiciness = $orange->juiciness;   # returns 8  
31    
32   # a "ref" must be set to a blessed object  Class::Tangram is a tool for defining objects attributes.  Simply
33   my $grower = bless { name => "Joe" }, "Farmer";  define your object's fields/attributes using the same syntax
34   $orange->set_grower ($grower);  introduced in _A Guided Tour of Tangram_, and you get objects that
35    work As You'd Expect(tm).
36    
37    Class::Tangram has no dependancy upon Tangram, and vice versa.
38    Neither requires anything special of your objects, nor do they insert
39    any special fields into your objects.  This is a very important
40    feature with innumerable benefits, and few (if any) other object
41    persistence tools have this feature.
42    
43    So, fluff aside, let's run through how you use Class::Tangram to make
44    objects.
45    
46    First, you decide upon the attributes your object is going to have.
47    You might do this using UML, or you might pick an existing database
48    table and declare each column to be an attribute (you can leave out
49    "id"; that one is implicit).
50    
51    Your object should use Class::Tangram as a base class;
52    
53      use base qw(Class::Tangram)
54    
55    or for older versions of perl:
56    
57      use Class::Tangram;
58      use vars qw(@ISA);
59      @ISA = qw(Class::Tangram)
60    
61    You should then define a C<$fields> variable in the scope of the
62    package, that is a B<hash> from attribute B<types> (see
63    L<Tangram::Type>) to either an B<array> of B<attribute names>, or
64    another B<hash> from B<attribute names> to B<options hashes> (or
65    C<undef>).  The layout of this structure coincides with the C<fields>
66    portion of a tangram schema (see L<Tangram::Schema>).  Note: the term
67    `schema' is used frequently to refer to the C<$fields> structure.
68    
69   # these are all illegal  For example,
  eval { $orange->set_juiciness ("Yum"); }; print $@;  
  eval { $orange->set_segments (31); }; print $@;  
  eval { $orange->set_grower ("Mr. Nice"); }; print $@;  
70    
71   # if you prefer   package MyObject;
72   $orange->get( "juiciness" );   use base qw(Class::Tangram);
  $orange->set( juiciness => 123 );  
73    
74  =head1 DESCRIPTION   our $fields = {
75         int => {
76             juiciness => undef,
77             segments => {
78                 # this code reference is called when this
79                 # attribute is set, to check the value is
80                 # OK
81                 check_func => sub {
82                     die "too many segments"
83                         if (${(shift)} > 30);
84                 },
85                 # the default for this attribute.
86                 init_default => 7,
87             },
88         },
89         ref => {
90            grower => undef,
91         },
92    
93  Class::Tangram is a base class originally intended for use with       # 'required' attributes - insist that these fields are
94  Tangram objects, that gives you free constructors, access methods,       # set, both with constructor and set()/set_X methods
95  update methods, and a destructor that should help in breaking circular       string => {
96  references for you. Type checking is achieved by parsing the schema           # true: 'type' must have non-empty value (for
97  for the object, which is contained within the object class in an           # strings) or be logically true (for other types)
98  exported variable C<$schema>.           type => { required => 1 },
99    
100  After writing this I found that it was useful for merely adding type           # false: 'tag' must be defined but may be empty
101  checking and validation to arbitrary objects.  There are several           tag => { required => '' },
102  modules on CPAN to do that already, but many don't have finely grained       },
103  type checking, and none of them integrated with Tangram.  
104         # fields allowed by Class::Tangram but not ever
105         # stored by Tangram - no type checking by default
106         transient => [ qw(_tangible) ],
107     };
108    
109    It is of critical importance to your sanity that you understand how
110    anonymous hashes and anonymous arrays work in Perl.  Some additional
111    features are used above that have not yet been introduced, but you
112    should be able to look at the above data structure and see that it
113    satisfies the conditions stated in the paragraph before it.  If it is
114    hazy, I recommend reading L<perlref> or L<perlreftut>.
115    
116    When the schema for the object is first imported (see L<Schema
117    import>), Class::Tangram defines accessor functions for each of the
118    attributes defined in the schema.  These accessor functions are then
119    available as C<$object-E<gt>function> on created objects.  By virtue
120    of inheritance, various other methods are available.
121    
122    From Class::Tangram 1.13 onwards, no use of perl's C<AUTOLOAD>
123    functionality is used.
124    
125  =cut  =cut
126    
127  use strict;  use strict;
128  use Carp qw(croak cluck);  use Carp qw(croak cluck);
129    
130  use vars qw($AUTOLOAD $VERSION);  use vars qw($VERSION %defaults);
131  $VERSION = "1.04";  
132    use Set::Object;
133    
134  local $AUTOLOAD;  use Pod::Constants -trim => 1,
135        'MODULE RELEASE' => sub { ($VERSION) = /(\d+\.\d+)/ },
136        'Default Type Checking' => sub { %defaults = eval; };
137    
138  # $types{$class}->{$attribute} is the tangram type of each attribute  # $types{$class}->{$attribute} is the tangram type of each attribute
139  my (%types);  my (%types);
140    
141    # $attribute_options{$class}->{$attribute} is the hash passed to tangram
142    # for the given attribute
143    my (%attribute_options);
144    
145  # $check{$class}->{$attribute}->($value) is a function that will die  # $check{$class}->{$attribute}->($value) is a function that will die
146  # if $value is not alright, see check_X functions  # if $value is not alright, see check_X functions
147  my (%check);  my (%check);
# Line 128  my (%cleaners); Line 155  my (%cleaners);
155  # $init_defaults{$class}->{$attribute}  # $init_defaults{$class}->{$attribute}
156  my (%init_defaults);  my (%init_defaults);
157    
158    # $required_attributes{$class}->{$attribute} records which attributes
159    # are required... used only by new() at present.
160    my (%required_attributes);
161    
162  # if a class is abstract, complain if one is constructed.  # if a class is abstract, complain if one is constructed.
163  my (%abstract);  my (%abstract);
164    
165    # records the inheritances of classes.
166    my (%bases);
167    
168  =head1 METHODS  =head1 METHODS
169    
170    The following methods are available for all Class::Tangram objects
171    
172    =head2 Constructor
173    
174    A Constructor is a method that returns a new instance of an object.
175    
176  =over 4  =over 4
177    
178  =item Class-E<gt>new (attribute1 =E<gt> value, attribute2 =E<gt> value)  =item Class-E<gt>new (attribute1 =E<gt> value, attribute2 =E<gt> value)
179    
180  sets up a new object of type Class, with attributes set to the values  Sets up a new object of type C<Class>, with attributes set to the
181  supplied.  values supplied.
182    
183  Can also be used as an object method, in which case it returns a  Can also be used as an object method (normal use is as a "class
184  B<copy> of the object, without any deep copying.  method"), in which case it returns a B<copy> of the object, without
185    any deep copying.
186    
187  =cut  =cut
188    
# Line 150  sub new ($@) Line 191  sub new ($@)
191      my $invocant = shift;      my $invocant = shift;
192      my $class = ref $invocant || $invocant;      my $class = ref $invocant || $invocant;
193    
194      my @values = @_;      (my @values, @_) = @_;
195    
196      # Setup the object      # Setup the object
197      my $self = { };      my $self = { };
198      bless $self, $class;      bless $self, $class;
199    
200      exists $check{$class} or import_schema($class);      # auto-load schema as necessary
201        exists $types{$class} or import_schema($class);
202    
203      croak "Attempt to instantiate an abstract type"      croak "Attempt to instantiate an abstract type"
204          if ($abstract{$class});          if ($abstract{$class});
205    
206      if ($invocant ne $class)      if (ref $invocant)
207      {      {
208          # The copy constructor; this could be better :)          # The copy constructor; this could be better :)
209          # this has the side effect of much auto-vivification.          # this has the side effect of much auto-vivification.
# Line 171  sub new ($@) Line 213  sub new ($@)
213      else      else
214      {      {
215          $self->set (@values); # start with @values          $self->set (@values); # start with @values
216        }
217    
218        $self->_fill_init_default();
219        $self->_check_required();
220    
221          # now fill in fields that have defaults      return $self;
222          for my $attribute (keys %{$init_defaults{$class}}) {  
223    }
224    
225              next if (exists $self->{$attribute});  sub _fill_init_default {
226        my $self = shift;
227        my $class = ref $self or die "_fill_init_default usage error";
228    
229        # fill in fields that have defaults
230        while ( my ($attribute, $default) =
231                each %{$init_defaults{$class}} ) {
232    
233            next if (exists $self->{$attribute});
234    
235            my $setter = "set_$attribute";
236            if (ref $default eq "CODE") {
237                # sub { }, attribute gets return value
238                $self->$setter( $default->($self) );
239    
240            } elsif (ref $default eq "HASH") {
241                # hash ref, copy hash
242                $self->$setter( { %{ $default } } );
243    
244            } elsif (ref $default eq "ARRAY") {
245                # array ref, copy array
246                $self->$setter( [ @{ $default } ] );
247    
248              my $default = $init_defaults{$class}->{$attribute}          } else {
249                  unless tied $init_defaults{$class}->{$attribute};              # something else, an object or a scalar
250                $self->$setter($default);
251            }
252        }
253    }
254    
255              if (ref $default eq "CODE") {  sub _check_required {
256                  # sub { }, attribute gets return value      my $self = shift;
257                  $self->{$attribute}      my $class = ref $self;
258                      = $init_defaults{$class}->{$attribute}->();  
259        # make sure field is not undef if 'required' option is set
260              } elsif (ref $default eq "HASH") {      if (my $required = $required_attributes{$class}) {
261                  # hash ref, copy hash  
262                  $self->{$attribute}          # find the immediate caller outside of this package
263                      = { %{ $init_defaults{$class}->{$attribute} } };          my $i = 0;
264            $i++ while UNIVERSAL::isa($self, scalar(caller($i))||";->");
265              } elsif (ref $default eq "ARRAY") {  
266                  # array ref, copy array          # give Tangram some lenience - it is exempt from the effects
267                  $self->{$attribute}          # of the "required" option
268                      = [ @{ $init_defaults{$class}->{$attribute} } ];          unless ( caller($i) =~ m/^Tangram::/ ) {
269                my @missing;
270              } else {              while ( my ($attribute, $value) = each %$required ) {
271                  # something else, an object or a scalar                  push(@missing, $attribute)
272                  $self->{$attribute}                      if ! exists $self->{$attribute};
                     = $init_defaults{$class}->{$attribute};  
273              }              }
274                croak("object missing required attribute(s): "
275                      .join(', ',@missing).'.') if @missing;
276          }          }
277      }      }
     return $self;  
278  }  }
279    
280    =back
281    
282    =head2 Accessing & Setting Attributes
283    
284    =over
285    
286  =item $instance->set(attribute => $value, ...)  =item $instance->set(attribute => $value, ...)
287    
288  Sets the attributes of the given instance to the given values.  croaks  Sets the attributes of the given instance to the given values.  croaks
289  if there is a problem with the values.  if there is a problem with the values.
290    
291    This function simply calls C<$instance-E<gt>set_attribute($value)> for
292    each of the C<attribute =E<gt> $value> pairs passed to it.
293    
294  =cut  =cut
295    
296  sub set($@) {  sub set {
297      my ($self, @values) = (@_);      my $self = shift;
298    
299      # yes, this is a lot to do.  yes, it's slow.  But I'm fairly      # yes, this is a lot to do.  yes, it's slow.  But I'm fairly
300      # certain that this could be handled efficiently if it were to be      # certain that this could be handled efficiently if it were to be
301      # moved inside the Perl interpreter or an XS module      # moved inside the Perl interpreter or an XS module
302      $self->isa("Class::Tangram") or croak "type mismatch";      UNIVERSAL::isa($self, "Class::Tangram") or croak "type mismatch";
303      my $class = ref $self;      my $class = ref $self;
304      exists $check{$class} or import_schema($class);      exists $check{$class} or import_schema($class);
305        croak "set must be called with an even number of arguments"
306            if (scalar(@_) & 1);
307    
308      while (my ($name, $value) = splice @values, 0, 2) {      while (my ($name, $value) = splice @_, 0, 2) {
         croak "attempt to set an illegal field $name in a $class"  
             if (!defined $check{$class}->{$name});  
309    
310          #local $@;          my $setter = "set_".$name;
311    
312          # these handlers die on failure          croak "attempt to set an illegal field $name in a $class"
313          eval { $check{$class}->{$name}->(\$value) };              unless $self->can($setter);
         $@ && croak ("value failed type check - ${class}->{$name}, "  
                      ."\"$value\" ($@)");  
314    
315          #should be ok now          $self->$setter($value);
         $self->{$name} = $value;  
316      }      }
317  }  }
318    
319  =item $instance->get($attribute)  =item $instance->get("attribute")
320    
321  Gets the value of $attribute.  If the attribute in question is a set,  Gets the value of C<$attribute>.  This simply calls
322  and this method is called in list context, then it returns the MEMBERS  C<$instance-E<gt>get_attribute>.  If multiple attributes are listed,
323  of the set (if called in scalar context, it returns the Set::Object  then a list of the attribute values is returned in order.  Note that
324  container).  you get back the results of the scalar context C<get_attribute> call
325    in this case.
326    
327  =cut  =cut
328    
329  sub get($$) {  sub get {
330      my ($self, $field) = (@_);      my $self = shift;
331      $self->isa("Class::Tangram") or croak "type mismatch";      croak "get what?" unless @_;
332        UNIVERSAL::isa($self, "Class::Tangram") or croak "type mismatch";
333    
334      my $class = ref $self;      my $class = ref $self;
335      exists $check{$class} or import_schema($class);      exists $check{$class} or import_schema($class);
     croak "attempt to read an illegal field $field in a $class"  
         if (!defined $check{$class}->{$field});  
336    
337      if ($types{$class}->{$field} =~ m/^i?set$/o) {      my $multiget = (scalar(@_) != 1);
338          if (!defined $self->{$field}) {  
339              $self->{$field} = Set::Object->new();      my @return;
340          }      while ( my $field = shift ) {
341          if (wantarray) {          my $getter = "get_".$field;
342              return $self->{$field}->members;          croak "attempt to read an illegal field $field in a $class"
343                unless $self->can($getter);
344    
345            if ( $multiget ) {
346                push @return, scalar($self->$getter());
347            } else {
348                return $self->$getter();
349          }          }
350      }      }
351    
352      return $self->{$field};      return @return;
353  }  }
354    
355  =item $instance->attribute($value)  =item $instance->attribute($value)
356    
357  If $value is not given, then  If C<$value> is not given, then
358  this is equivalent to $instance->get("attribute")  this is equivalent to C<$instance-E<gt>get_attribute>
359    
360  If $value is given, then this is equivalent to  If C<$value> is given, then this is equivalent to
361  $instance->set("attribute", $value).  This usage issues a warning; you  C<$instance-E<gt>set_attribute($value)>.  This usage issues a warning
362  should change your code to use the set_attribute syntax for better  if warnings are on; you should change your code to use the
363  readability.  set_attribute syntax for better readability.  OO veterans will tell
364    you that for maintainability object method names should always be a
365    verb.
366    
367  =item $instance->get_attribute  =item $instance->get_attribute
368    
369  =item $instance->set_attribute($value)  =item $instance->set_attribute($value)
370    
371  Equivalent to $instance->get("attribute") and $instance->set(attribute  The normative way of getting and setting attributes.  If you wish to
372  => $value), respectively.  override the behaviour of an object when getting or setting an
373    attribute, override these functions.  They will be called when you use
374    C<$instance-E<gt>attribute>, C<$instance-E<gt>get()>, constructors,
375    etc.
376    
377  =item $instance->attribute_includes(@objects)  =item $instance->attribute_includes(@objects)
378    
# Line 294  Equivalent to $instance->get("attribute" Line 384  Equivalent to $instance->get("attribute"
384    
385  =item $instance->attribute_remove(@objects)  =item $instance->attribute_remove(@objects)
386    
387  Equivalent to calling $instance->attribute->includes(@objects), etc.  This suite of functions applies to attributes that are sets (C<iset>
388  This only works if the attribute in question is a Set::Object.  or C<set>).  It could in theory also apply generally to all
389    collections - ie also arrays (C<iarray> or C<array>), and hashes
390    (C<hash>, C<ihash>).  This will be implemented subject to user demand.
391    
392    =back
393    
394    B<Note:> The above functions can be overridden, but they may not be
395    called with the C<$self-E<gt>SUPER::> superclass chaining method.
396    This is because they are not defined within the scope of
397    Class::Tangram, only your package.
398    
399  =cut  =cut
400    
401  sub AUTOLOAD ($;$) {  =head1 ATTRIBUTE TYPE CHECKING
     my ($self, $value) = (@_);  
     $self->isa("Class::Tangram") or croak "type mismatch";  
402    
403      my $class = ref $self;  Class::Tangram provides type checking of attributes when attributes
404      $AUTOLOAD =~ s/.*://;  are set - either using the default C<set_attribute> functions, or
405      if ($AUTOLOAD =~ m/^(set_|get_)?([^:]+)$/  created via the C<new> constructor.
         and defined $types{$class}->{$2}) {  
   
         # perl sucks at this type of test  
         if ((defined $1 and $1 eq "set_")  
             or (!defined $1 and defined $value)) {  
   
             if ($^W && !defined $1) {  
                 cluck("The OO police say change your call to "  
                       ."\$obj->set_$2");  
             }  
             return set($self, $2, $value);  
         } else {  
             return get($self, $2);  
         }  
     } elsif (my ($attr, $method) =  
              ($AUTOLOAD =~ m/^(.*)_(includes|insert|  
                              size|clear|remove)$/x)  
              and $types{$class}->{$1} =~ m/^i?set$/) {  
         return get($self, $attr)->$method(@_[1..$#_]);  
     } else {  
         croak("unknown method/attribute ${class}->$AUTOLOAD called");  
     }  
 }  
406    
407  =item $instance->getset($attribute, $value)  The checking has default behaviour for each type of attribute (see
408    L<Default Type Checking>), and can be extended arbitrarily via a
409    per-attribute C<check_func>, described below.  Critical attributes can
410    be marked as such with the C<required> flag.
411    
412  If you're replacing the AUTOLOAD function in your Class::Tangram  The specification of this type checking is placed in the class schema,
413  derived class, but would still like to use the behaviour for one or  in the per-attribute B<options hash>.  This is a Class::Tangram
414  two fields, then you can define functions for them to fall through to  extension to the Tangram schema structure.
 the Class::Tangram method, like so:  
415    
416   sub attribute { $_[0]->SUPER::getset("attribute", $_[1]) }  =over
417    
418  =cut  =item check_func
419    
420  sub getset($$;$) {  A function that is called with a B<reference> to the new value in
421      my ($self, $attr, $value) = (@_);  C<$_[0]>.  It should call C<die()> if the value is bad.  Note that
422      $self->isa("Class::Tangram") or croak "type mismatch";  this check_func will never be passed an undefined value; this is
423    covered by the "required" option, below.
424    
425      if (defined $value) {  In the example schema (above), the attribute C<segments> has a
426          return set($self, $attr, $value);  C<check_func> that prevents setting the value to anything greater than
427      } else {  30.  Note that it does not prevent you from setting the value to
428          return get($self, $attr);  something that is not an integer; if you define a C<check_func>, it
429      }  replaces the default.
430    
431  }  =item required
432    
433    If this option is set to a true value, then the attribute must be set
434    to a true value to pass type checking.  For string attributes, this
435    means that the string must be defined and non-empty (so "0" is true).
436    For other attribute types, the normal Perl definition of logical truth
437    is used.
438    
439    If the required option is defined but logically false, (ie "" or 0),
440    then the attribute must also be defined, but may be set to a logically
441    false value.
442    
443    If the required option is undefined, then the attribute may be set to
444    an undefined value.
445    
446    For integration with tangram, the C<new()> function has a special
447    hack; if it is being invoked from within Tangram, then the required
448    test is skipped.
449    
450    =back
451    
452    =head2 Other per-attribute options
453    
454    Any of the following options may be inserted into the per-attribute
455    B<options hash>:
456    
457    =over
458    
459    =item init_default
460    
461    This value specifies the default value of the attribute when
462    it is created with C<new()>.  It is a scalar value, it is
463    copied to the fresh object.  If it is a code reference, that
464    code reference is called and its return value inserted into
465    the attribute.  If it is an ARRAY or HASH reference, then
466    that array or hash is COPIED into the attribute.
467    
468    =item destroy_func
469    
470    If anything special needs to happen to this attribute before the
471    object is destroyed (or when someone calls
472    C<$object-E<gt>clear_refs()>), then define this.  It is called as
473    C<$sub-E<gt>($object, "attribute")>.
474    
475    =back
476    
477    =head2 Default Type Checking
478    
479     # The following list is eval'ed from this documentation
480     # when Class::Tangram loads, and used as default attribute
481     # options for the specified types.  So, eg, the default
482     # "init_default" for "set" types is a subroutine that
483     # returns a new Set::Object container.
484    
485     # "parse" is special - it is passed the options hash given
486     # by the user and should return (\&check_func,
487     # \&destroy_func).  This is how the magical string type
488     # checking is performed - see the entry for parse_string(),
489     # below.
490    
491     int         => { check_func   => \&check_int },
492     real        => { check_func   => \&check_real },
493     string      => { parse        => \&parse_string },
494     ref         => { check_func   => \&check_obj,
495                      destroy_func => \&destroy_ref },
496     array       => { check_func   => \&check_array,
497                      destroy_func => \&destroy_array },
498     iarray      => { check_func   => \&check_array,
499                      destroy_func => \&destroy_array },
500     flat_array  => { check_func   => \&check_flat_array },
501     set         => { check_func   => \&check_set,
502                      destroy_func => \&destroy_set,
503                      init_default => sub { Set::Object->new() } },
504     iset        => { check_func   => \&check_set,
505                      destroy_func => \&destroy_set,
506                      init_default => sub { Set::Object->new() } },
507     dmdatetime  => { check_func   => \&check_dmdatetime },
508     rawdatetime => { check_func   => \&check_rawdatetime },
509     rawdate     => { check_func   => \&check_rawdate },
510     rawtime     => { check_func   => \&check_rawtime },
511     flat_hash   => { check_func   => \&check_flat_hash },
512     transient   => { check_func   => \&check_nothing },
513     hash        => { check_func   => \&check_hash,
514                      destroy_func => \&destroy_hash,
515                      get_func     => \&get_hash },
516     perl_dump   => { check_func   => \&check_nothing }
517    
518    =over
519    
520  =item check_X (\$value)  =item check_X (\$value)
521    
522  This series of functions checks that $value is of the type X, and  This series of internal functions are built-in C<check_func> functions
523  within applicable bounds.  If there is a problem, then it will croak()  defined for all of the standard Tangram attribute types.
 the error.  These functions are not called from the code, but by the  
 set() method on a particular attribute.  
524    
525  Available functions are:  =over
526    
527    check_string - checks that the supplied value is less  =item check_string
528                   than 255 characters long.  
529    checks that the supplied value is less than 255 characters long.
530    
531  =cut  =cut
532    
533  sub check_string {  sub check_string {
534      croak "string ${$_[0]} too long"      croak "string too long"
535          if (length ${$_[0]} > 255);          if (length ${$_[0]} > 255);
536  }  }
537    
538  =pod  =item check_int
539    
540    check_int    - checks that the value is a (possibly  checks that the value is a (possibly signed) integer
                  signed) integer  
541    
542  =cut  =cut
543    
544  my $int_re = qr/^-?\d+$/;  my $int_re = qr/^-?\d+$/;
545  sub check_int {  sub check_int {
546      croak "not an int"      croak "not an integer"
547          if (${$_[0]} !~ m/$int_re/ms);          if (${$_[0]} !~ m/$int_re/o);
548  }  }
549    
550  =pod  =item check_real
551    
552    check_real   - checks that the value is a real number  checks that the value is a real number, by stringifying it and
553                   (m/^\d*(\.\d*)?(e\d*)?$/)  matching it against (C<m/^-?\d*(\.\d*)?(e-?\d*)?$/>).  Inefficient?
554    Yes.  Patches welcome.
555    
556  =cut  =cut
557    
558  my $real_re = qr/^-?\d*(\.\d*)?(e-?\d*)?$/;  my $real_re = qr/^-?\d*(\.\d*)?(e-?\d*)?$/;
559  sub check_real {  sub check_real {
560      croak "not a real"      croak "not a real number"
561          if (${$_[0]} !~ m/$real_re/ms);          if (${$_[0]} !~ m/$real_re/o);
562  }  }
563    
564  =pod  =item check_obj
565    
566    check_obj    - checks that the supplied variable is a  checks that the supplied variable is a reference to a blessed object
                  reference to a blessed object  
567    
568  =cut  =cut
569    
# Line 409  sub check_real { Line 571  sub check_real {
571  my $obj_re = qr/^(?:HASH|ARRAY|SCALAR)?$/;  my $obj_re = qr/^(?:HASH|ARRAY|SCALAR)?$/;
572  sub check_obj {  sub check_obj {
573      croak "not an object reference"      croak "not an object reference"
574          if ((ref ${$_[0]}) =~ m/$obj_re/);          if ((ref ${$_[0]}) =~ m/$obj_re/o);
575  }  }
576    
577  =pod  =item check_flat_array
578    
579    check_flat_array  checks that $value is a ref ARRAY and that all elements are unblessed
580                 - checks that $value is a ref ARRAY  scalars.  Does NOT currently check that all values are of the correct
581    type (int vs real vs string, etc)
582    
583  =cut  =cut
584    
585  sub check_flat_array {  sub check_flat_array {
586      croak "not a flat array"      croak "not a flat array"
587          if (ref ${$_[0]} ne "ARRAY");          if (ref ${$_[0]} ne "ARRAY");
588        croak "flat array may not contain references"
589            if (map { (ref $_ ? "1" : ()) } @{$_[0]});
590  }  }
591    
592  =pod  =item check_array
593    
594    check_array  - checks that $value is a ref ARRAY, and that  checks that $value is a ref ARRAY, and that each element in the array
595                   each element in the array is a reference to  is a reference to a blessed object.
                  a blessed object.  
596    
597  =cut  =cut
598    
# Line 437  sub check_array { Line 601  sub check_array {
601          if (ref ${$_[0]} ne "ARRAY");          if (ref ${$_[0]} ne "ARRAY");
602      for my $a (@{${$_[0]}}) {      for my $a (@{${$_[0]}}) {
603          croak "member in array not an object reference"          croak "member in array not an object reference"
604              if ((ref $a) =~ m/$obj_re/);              if ((ref $a) =~ m/$obj_re/o);
605      }      }
606  }  }
607    
608  =pod  =item check_set
609    
610    check_set    - checks that $value->isa("Set::Object")  checks that $value->isa("Set::Object")
611    
612  =cut  =cut
613    
614  sub check_set {  sub check_set {
615      croak "set type not passed a Set::Object"      croak "set type not passed a Set::Object"
616          unless (ref ${$_[0]} and ${$_[0]}->isa("Set::Object"));          unless (UNIVERSAL::isa(${$_[0]}, "Set::Object"));
617  }  }
618    
619  =pod  =item check_rawdate
620    
621    check_rawdatetime  checks that $value is of the form YYYY-MM-DD, or YYYYMMDD, or YYMMDD.
                - checks that $value is of the form  
                  YYYY-MM-DD HH:MM:SS  
622    
623  =cut  =cut
624    
625  # YYYY-MM-DD HH:MM:SS  # YYYY-MM-DD HH:MM:SS
626  my $rawdatetime_re = qr/^\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}:\d{2}$/;  my $rawdate_re = qr/^(?:  \d{4}-\d{2}-\d{2}
627  sub check_rawdatetime {                       |    (?:\d\d){3,4}
628      croak "invalid SQL rawdatetime"                       )$/x;
629          unless (${$_[0]} =~ m/$rawdatetime_re/);  sub check_rawdate {
630        croak "invalid SQL rawdate"
631            unless (${$_[0]} =~ m/$rawdate_re/o);
632  }  }
633    
634  =pod  =item check_rawtime
635    
636    check_time  checks that $value is of the form HH:MM(:SS)?
               - checks that $value is of the form  
                 HH:MM(:SS)?  
637    
638  =cut  =cut
639    
640  my $time_re = qr/^\d{1,2}:\d{2}(?::\d{2})?$/;  # YYYY-MM-DD HH:MM:SS
641  sub check_time {  my $rawtime_re = qr/^\d{1,2}:\d{2}(?::\d{2})?$/;
642      croak "invalid SQL time"  sub check_rawtime {
643          unless (${$_[0]} =~ m/$time_re/);      croak "invalid SQL rawtime"
644            unless (${$_[0]} =~ m/$rawtime_re/o);
645  }  }
646    
647  =pod  =item check_rawdatetime
648    
649    check_timestamp  checks that $value is of the form YYYY-MM-DD HH:MM(:SS)? (the time
650                - checks that $value is of the form  and/or the date can be missing), or a string of numbers between 6 and
651                  (YYYY-MM-DD )?HH:MM(:SS)?  14 numbers long.
652    
653  =cut  =cut
654    
655  my $timestamp_re = qr/^(?:\d{4}-\d{2}-\d{2}\s+)?\d{1,2}:\d{2}(?::\d{2})?$/;  my $rawdatetime_re = qr/^(?:
656  sub check_timestamp {                              # YYYY-MM-DD HH:MM:SS
657      croak "invalid SQL timestamp"                              (?: (?:\d{4}-\d{2}-\d{2}\s+)?
658          unless (${$_[0]} =~ m/$timestamp_re/);                                  \d{1,2}:\d{2}(?::\d{2})?
659                                |   \d{4}-\d{2}-\d{2}
660                                )
661                             |  # YYMMDD, etc
662                                (?:\d\d){3,7}
663                             )$/x;
664    sub check_rawdatetime {
665        croak "invalid SQL rawdatetime dude"
666            unless (${$_[0]} =~ m/$rawdatetime_re/o);
667  }  }
668    
669  =pod  =item check_dmdatetime
670    
671    check_flat_hash  checks that $value is of the form YYYYMMDDHH:MM:SS, or those allowed
672                 - checks that $value is a ref HASH  for rawdatetime.
673    
674    =cut
675    
676    sub check_dmdatetime {
677        croak "invalid SQL rawdatetime dude"
678            unless (${$_[0]} =~ m/^\d{10}:\d\d:\d\d$|$rawdatetime_re/o);
679    }
680    
681    =item check_flat_hash
682    
683    checks that $value is a ref HASH and all values are scalars.  Does NOT
684    currently check that all values are of the correct type (int vs real
685    vs string, etc)
686    
687  =cut  =cut
688    
# Line 511  sub check_flat_hash { Line 695  sub check_flat_hash {
695      }      }
696  }  }
697    
698  =pod  =item check_hash
699    
700    check_hash   - checks that $value is a ref HASH, that  checks that $value is a ref HASH, that every key in the hash is a
701                   every key in the hash is a scalar, and that  scalar, and that every value is a blessed object.
                  every value is a blessed object.  
702    
703  =cut  =cut
704    
# Line 530  sub check_hash { Line 713  sub check_hash {
713      }      }
714  }  }
715    
716  =pod  =item check_nothing
717    
718    check_nothing - checks whether Australians like sport  checks whether Australians like sport
719    
720  =cut  =cut
721    
722  sub check_nothing { }  sub check_nothing { }
723    
724    =back
725    
726  =item destroy_X ($instance, $attr)  =item destroy_X ($instance, $attr)
727    
728  Similar story with the check_X series of functions, these are called  Similar story with the check_X series of functions, these are called
# Line 548  destroyed also>.  In other words, do not Line 733  destroyed also>.  In other words, do not
733  share Set::Object containers or hash references in their attributes,  share Set::Object containers or hash references in their attributes,
734  otherwise when one gets destroyed the others will lose their data.  otherwise when one gets destroyed the others will lose their data.
735    
736  Available functions are:  Available functions:
737    
738    =over
739    
740    =item destroy_array
741    
742    destroy_array - empties an array  empties an array
743    
744  =cut  =cut
745    
746  sub destroy_array {  sub destroy_array {
747      my ($self, $attr) = (@_);      my $self = shift;
748        my $attr = shift;
749      my $t = tied $self->{$attr};      my $t = tied $self->{$attr};
750      @{$self->{$attr}} = () unless ($t =~ m,Tangram::CollOnDemand,);      @{$self->{$attr}} = ()
751            unless (defined $t and $t =~ m,Tangram::CollOnDemand,);
752      delete $self->{$attr};      delete $self->{$attr};
753  }  }
754    
755  =pod  =item destroy_set
756    
757    destroy_set   - calls Set::Object::clear to clear the set  Calls Set::Object::clear to clear the set
758    
759  =cut  =cut
760    
761  sub destroy_set {  sub destroy_set {
762      my ($self, $attr) = (@_);      my $self = shift;
763        my $attr = shift;
     # warnings suck sometimes  
     local $^W = 0;  
764    
765      my $t = tied $self->{$attr};      my $t = tied $self->{$attr};
766      return if ($t =~ m,Tangram::CollOnDemand,);      return if (defined $t and $t =~ m,Tangram::CollOnDemand,);
767      if (ref $self->{$attr} eq "Set::Object") {      if (ref $self->{$attr} eq "Set::Object") {
768          $self->{$attr}->clear;          $self->{$attr}->clear;
769      }      }
770      delete $self->{$attr};      delete $self->{$attr};
771  }  }
772    
773  =pod  =item destroy_hash
774    
775    destroy_hash  - empties a hash  empties a hash
776    
777  =cut  =cut
778    
779  sub destroy_hash {  sub destroy_hash {
780      my ($self, $attr) = (@_);      my $self = shift;
781        my $attr = shift;
782      my $t = tied $self->{$attr};      my $t = tied $self->{$attr};
783      %{$self->{$attr}} = () unless ($t =~ m,Tangram::CollOnDemand,);      %{$self->{$attr}} = ()
784            unless (defined $t and $t =~ m,Tangram::CollOnDemand,);
785      delete $self->{$attr};      delete $self->{$attr};
786  }  }
787    
788  =pod  =item destroy_ref
789    
790    destroy_ref   - destroys a reference.  Contains a hack for  destroys a reference.
                   Tangram so that if this ref is not loaded,  
                   it will not be autoloaded when it is  
                   attempted to be accessed.  
791    
792  =cut  =cut
793    
794  sub destroy_ref {  sub destroy_ref {
795      my ($self, $attr) = (@_);      my $self = shift;
796        delete $self->{shift};
     # warnings suck sometimes  
     local $^W = 0;  
   
     # the only reason I bother with all of this is that I experienced  
     # Perl did not always call an object's destructor if you just used  
     # delete.  
     my $t = tied $self->{$attr};  
     if (defined $t and $t =~ m/OnDemand/) {  
         delete $self->{$attr};  
     } else {  
         my $ref = delete $self->{$attr};  
     }  
797  }  }
798    
799    =back
800    
801  =item parse_X ($attribute, { schema option })  =item parse_X ($attribute, { schema option })
802    
803  Parses the schema option field, and returns one or two closures that  Parses the schema option field, and returns one or two closures that
# Line 627  act as a check_X and a destroy_X functio Line 805  act as a check_X and a destroy_X functio
805    
806  This is currently a very ugly hack, parsing the SQL type definition of  This is currently a very ugly hack, parsing the SQL type definition of
807  an object.  But it was bloody handy in my case for hacking this in  an object.  But it was bloody handy in my case for hacking this in
808  quickly.  This is unmanagably unportable across databases.  This  quickly.  This is probably unmanagably unportable across databases;
809  should be replaced by primitives that go the other way, building the  but send me bug reports on it anyway, and I'll try and make the
810  SQL type definition from a more abstract definition of the type.  parsers work for as many databases as possible.
811    
812    This perhaps should be replaced by primitives that go the other way,
813    building the SQL type definition from a more abstract definition of
814    the type.
815    
816  Available functions:  Available functions:
817    
818    parse_string  - parses SQL types of:  =over
819    
820    =item parse_string
821    
822    parses SQL types of:
823    
824    =over
825    
826  =cut  =cut
827    
828    use vars qw($quoted_part $sql_list);
829    
830    $quoted_part = qr/(?: \"([^\"]+)\" | \'([^\']+)\' )/x;
831    $sql_list = qr/\(\s*
832                      (
833                          $quoted_part
834                            (?:\s*,\s* $quoted_part )*
835                      ) \s*\)/x;
836    
837  sub parse_string {  sub parse_string {
838    
839      my ($attribute, $option) = (@_);      my $attribute = shift;
840        my $option = shift;
841    
842      # simple case; return the check_string function.  We don't      # simple case; return the check_string function.  We don't
843      # need a destructor for a string so don't return one.      # need a destructor for a string so don't return one.
# Line 647  sub parse_string { Line 845  sub parse_string {
845          return \&check_string;          return \&check_string;
846      }      }
847    
848  =pod  =item CHAR(N), VARCHAR(N)
849    
850        CHAR(N), VARCHAR(N)  closure checks length of string is less than N characters
           closure checks length of string is less  
           than N characters  
851    
852  =cut  =cut
853    
# Line 662  sub parse_string { Line 858  sub parse_string {
858                  if (length ${$_[0]} > $max_length);                  if (length ${$_[0]} > $max_length);
859          };          };
860    
861  =pod  =item TINYBLOB, BLOB, LONGBLOB
862    
863        TINYBLOB, BLOB, LONGBLOB  checks max. length of string to be 255, 65535 or 16777215 chars
864        TINYTEXT, TEXT, LONGTEXT  respectively.  Also works with "TEXT" instead of "BLOB"
           checks max. length of string to be 255,  
           65535 or 16777215 chars respectively  
865    
866  =cut  =cut
867    
868      } elsif ($option->{sql} =~ m/^\s*(tiny|long|medium)?      } elsif ($option->{sql} =~ m/^\s*(?:tiny|long|medium)?
869                                   (blob|text)/ix) {                                   (?:blob|text)/ix) {
870          my $max_length = ($1 ? ($1 eq "tiny"?255:2**24 - 1)          my $max_length = ($1 ? ($1 eq "tiny"?255:2**24 - 1)
871                            : 2**16 - 1);                            : 2**16 - 1);
872          return sub {          return sub {
873              die "string too long for $attribute"              die "string too long for $attribute"
874                  if (length ${$_[0]} > $max_length);                  if (${$_[0]} and length ${$_[0]} > $max_length);
875          };          };
876    
877  =pod  =item SET("members", "of", "set")
878    
879        SET("members", "of", "set")  checks that the value passed is valid as a SQL set type, and that all
880            checks that the value passed is valid as  of the passed values are allowed to be a member of that set.
           a SQL set type, and that all of the  
           passed values are allowed to be a member  
           of that set  
881    
882  =cut  =cut
883    
884      } elsif ($option->{sql} =~      } elsif (my ($members) = $option->{sql} =~
885               m/^\s*set\s*\(               m/^\s*set\s*$sql_list/oi) {
886                 (\"[^\"]+\" (?:\s*,\s*\"[^\"]+\")* \s* )  
887                 \)\s*$/xi) {          my %members;
888          my $members = $1;          $members{lc($1 || $2)} = 1
889          my ($member, %members);              while ( $members =~ m/\G[,\s]*$quoted_part/cog );
890          while (($member, $members) =  
                ($members =~ m/^[,\s]*\"([^\"]+)\"(.*)$/)) {  
             $members{lc($member)} = 1;  
         }  
891          return sub {          return sub {
892              for my $x (split /,/, ${$_[0]}) {              for my $x (split /\s*,\s*/, ${$_[0]}) {
893                  croak ("SQL set badly formed or invalid member $x "                  croak ("SQL set badly formed or invalid member $x "
894                         ." (SET" . join(",", keys %members). ")")                         ." (SET" . join(",", keys %members). ")")
895                      if (not exists $members{lc($x)});                      if (not exists $members{lc($x)});
896              }              }
897          };          };
898    
899  =pod  =item ENUM("possible", "values")
900    
901        ENUM("possible", "values")  checks that the value passed is one of the allowed values.
           checks that the value passed is one of  
           the allowed values.  
902    
903  =cut  =cut
904    
905      } elsif ($option->{sql} =~      } elsif (my ($values) = $option->{sql} =~
906               m/^\s*enum\s*\(               m/^\s*enum\s*$sql_list/oi ) {
907                 (\"[^\"]+\" (?:\s*,\s*\"[^\"]+\")* \s* )  
908                 \)\s*/xi) {          my %values;
909          my $values = $1;          $values{lc($1 || $2)} = 1
910          my ($value, %values);              while ( $values =~ m/\G[,\s]*$quoted_part/gc);
911          while (($value, $values) =  
                ($values =~ m/^[,\s]*\"([^\"]+)\"(.*)$/)) {  
             $values{lc($value)} = 1;  
         }  
912          return sub {          return sub {
913              croak ("invalid enum value ${$_[0]} must be ("              croak ("invalid enum value ${$_[0]} must be ("
914                     . join(",", keys %values). ")")                     . join(",", keys %values). ")")
915                  if (not exists $values{lc(${$_[0]})});                  if (not exists $values{lc(${$_[0]})});
916          }          }
917    
918    
919      } else {      } else {
920          die ("Please build support for your string SQL type in "          die ("Please build support for your string SQL type in "
921               ."Class::Tangram (".$option->{sql}.")");               ."Class::Tangram (".$option->{sql}.")");
922      }      }
923  }  }
924    
925  # Here is where I map Tangram::Type types to functions.  =back
 # Format:  
 #  type => {  
 #      check => \&check_X  
 #      parse => \&parse_type  
 #      destroy => \&destroy_X  
 #  }  
 #  
 my %defaults =  
     (  
      int         => { check => \&check_int },  
      real        => { check => \&check_real },  
      string      => {             parse => \&parse_string },  
      ref         => { check => \&check_obj,     destroy => \&destroy_ref },  
      array       => { check => \&check_array,   destroy => \&destroy_array },  
      iarray      => { check => \&check_array,   destroy => \&destroy_array },  
      flat_array  => { check => \&check_flat_array },  
      set         => { check => \&check_set,     destroy => \&destroy_set },  
      iset        => { check => \&check_set,     destroy => \&destroy_set },  
      rawdatetime => { check => \&check_rawdatetime },  
      time        => { check => \&check_time },  
      timestamp   => { check => \&check_timestamp },  
      flat_hash   => { check => \&check_flat_hash },  
      hash        => { check => \&check_hash,    destroy => \&destroy_hash },  
      perl_dump   => { check => \&check_nothing }  
     );  
   
 =item import_schema($class)  
   
 Parses a tangram object schema, in "\$${class}::schema" to the  
 internal representation used to check types values by set().  Called  
 automatically on the first get(), set(), or new() for an object of a  
 given class.  
   
 This function updates Tangram schema option hashes, with the following  
 keys:  
   
   check_func   - supply/override the check_X function for  
                  this attribute.  
   
   destroy_func - supply/override the destroy_X function for  
                  this attribute  
   
 See the SYNOPSIS section for an example of supplying a check_func in  
 an object schema.  
   
 =cut  
   
 sub import_schema($) {  
     my ($class) = (@_);  
   
     eval {  
         my ($fields, $bases, $abstract);  
         {  
             no strict 'refs';  
             $fields = ${"${class}::schema"}->{fields};  
             $bases = ${"${class}::schema"}->{bases};  
             $abstract = ${"${class}::schema"}->{abstract};  
         }  
   
         my $check_class = { };  
         my $cleaners_class = { };  
         my $init_defaults_class = { };  
         my $types_class = { };  
   
         # if this is an abstract type, do not allow it to be  
         # instantiated  
         if ($abstract) {  
             $abstract{$class} = 1;  
         }  
   
         # If there are any base classes, import them first so that the  
         # check, cleaners and init_defaults can be inherited  
         if (defined $bases) {  
             (ref $bases eq "ARRAY")  
                 or die "bases not an array ref for $class";  
   
             # Note that the order of your bases is significant, that  
             # is if you are using multiple iheritance then the later  
             # classes override the earlier ones.  
             #for my $super ( Class::ISA::super_path($class) ) {  
             for my $super ( @$bases ) {  
                 import_schema $super unless (exists $check{$super});  
   
                 # copy each of the per-class configuration hashes to  
                 # this class as defaults.  
                 my ($k, $v);  
                 $types_class->{$k} = $v  
                     while (($k, $v) = each %{ $types{$super} } );  
                 $check_class->{$k} = $v  
                     while (($k, $v) = each %{ $check{$super} } );  
                 $cleaners_class->{$k} = $v  
                     while (($k, $v) = each %{ $cleaners{$super} } );  
                 $init_defaults_class->{$k} = $v  
                     while (($k, $v) = each %{ $init_defaults{$super} } );  
             }  
         }  
   
         # iterate over each of the *types* of fields (string, int, ref, etc.)  
         while (my ($type, $v) = each %$fields) {  
             if (ref $v eq "ARRAY") {  
                 $v = { map { $_, undef } @$v };  
             }  
             my $def = $defaults{$type};  
   
             # iterate each of the *attributes* of a particular type  
             while (my ($attribute, $option) = each %$v) {  
                 $types_class->{$attribute} = $type;  
   
                 # ----- check_X functions ----  
                 if (ref $option eq "HASH" and $option->{check_func}) {  
                     # user-supplied check_X function  
                     $check_class->{$attribute} =  
                         $option->{check_func};  
   
                 } else {  
                     if (not defined $def) {  
                         die "No default check function for type $type";  
                     }  
   
                     # check for a type-specific option hash parser  
                     if ($def->{parse}) {  
                         my ($check, $destroy) =  
                             $def->{parse}->($attribute, $option);  
   
                         $check_class->{$attribute} = $check;  
                         $cleaners_class->{$attribute} = $destroy  
                             if (defined $destroy);  
   
                     } else {  
                         # use the default for this type  
                         $check_class->{$attribute} = $def->{check};  
                     }  
                 }  
926    
927                  # ----- destroy_X functions  =back
                 if (ref $option eq "HASH" and $option->{destroy_func}) {  
                     # user-supplied destroy_X function  
                     $cleaners_class->{$attribute} =  
                         $option->{destroy_func};  
                 } else {  
                     if ($def->{destroy}) {  
                         # use the default for this type  
                         $cleaners_class->{$attribute} =  
                             $def->{destroy};  
                     }  
                 }  
928    
929                  # ----- init_default functions  =back
                 # create empty Set::Object containers as necessary  
                 if ($type =~ m/^i?set$/) {  
                     $init_defaults_class->{$attribute} =  
                         sub { Set::Object->new() };  
                 }  
                 if (ref $option eq "HASH" and $option->{init_default}) {  
                     $init_defaults_class->{$attribute} =  
                         $option->{init_default};  
                 }  
             }  
         }  
         $types{$class} = $types_class;  
         $check{$class} = $check_class;  
         $cleaners{$class} = $cleaners_class;  
         $init_defaults{$class} = $init_defaults_class;  
     };  
930    
931      $@ && die "$@ while trying to import schema for $class";  =head2 Quick Object Dumping and Destruction
 }  
932    
933    =over
934    
935  =item $instance->quickdump  =item $instance->quickdump
936    
# Line 921  currently escape unprintable characters. Line 945  currently escape unprintable characters.
945  =cut  =cut
946    
947  sub quickdump($) {  sub quickdump($) {
948      my ($self) = (@_);      my $self = shift;
949    
950      my $r = "REF ". (ref $self). "\n";      my $r = "REF ". (ref $self). "\n";
951      for my $k (sort keys %$self) {      for my $k (sort keys %$self) {
# Line 949  $k) Line 973  $k)
973  =cut  =cut
974    
975  sub DESTROY($) {  sub DESTROY($) {
976      my ($self) = (@_);      my $self = shift;
977    
978      my $class = ref $self;      my $class = ref $self;
979    
# Line 987  Tangram::Storage->unload method. Line 1011  Tangram::Storage->unload method.
1011  =cut  =cut
1012    
1013  sub clear_refs($) {  sub clear_refs($) {
1014      my ($self) = (@_);      my $self = shift;
   
1015      my $class = ref $self;      my $class = ref $self;
1016    
1017      exists $cleaners{$class} or import_schema($class);      exists $cleaners{$class} or import_schema($class);
# Line 1002  sub clear_refs($) { Line 1025  sub clear_refs($) {
1025      $self->{_NOREFS} = 1;      $self->{_NOREFS} = 1;
1026  }  }
1027    
1028    =back
1029    
1030    =head1 FUNCTIONS
1031    
1032    The following functions are not intended to be called as object
1033    methods.
1034    
1035    =head2 Schema Import
1036    
1037     our $fields = { int => [ qw(foo bar) ],
1038                     string => [ qw(baz quux) ] };
1039    
1040     # Version 1.115 and below compatibility:
1041     our $schema = {
1042        fields => { int => [ qw(foo bar) ],
1043                    string => [ qw(baz quux) ] }
1044        };
1045    
1046    =over
1047    
1048    =item Class::Tangram::import_schema($class)
1049    
1050    Parses a tangram object field list, in C<${"${class}::fields"}> (or
1051    C<${"${class}::schema"}-E<gt>{fields}> to the internal type information
1052    hashes.  It will also define all of the attribute accessor and update
1053    methods in the C<$class> package.
1054    
1055    Note that calling this function twice for the same class is not
1056    tested and may produce arbitrary results.  Patches welcome.
1057    
1058    =cut
1059    
1060    sub import_schema($) {    # Damn this function is long
1061        my $class = shift;
1062    
1063        eval {
1064            my ($fields, $bases, $abstract);
1065            {
1066    
1067                # Here, we go hunting around for their defined schema and
1068                # options
1069                no strict 'refs';
1070                local $^W=0;
1071                eval {
1072                    $fields = (${"${class}::fields"} ||
1073                               ${"${class}::schema"}->{fields});
1074                    $abstract = (${"${class}::abstract"} ||
1075                                 ${"${class}::schema"}->{abstract});
1076                    $bases = ${"${class}::schema"}->{bases};
1077                };
1078                if ( my @stack = @{"${class}::ISA"}) {
1079                    # clean "bases" information from @ISA
1080                    my %seen = map { $_ => 1 } $class, __PACKAGE__;
1081                    #$bases = [];   # will anything break without this? it's needed for recording inheritances later on
1082                    while ( my $super = pop @stack ) {
1083                        if ( defined ${"${super}::schema"}
1084                             or defined ${"${super}::fields"} ) {
1085                            push @$bases, $super;
1086                        } else {
1087                            push @stack, grep { !$seen{$_}++ }
1088                                @{"${super}::ISA"};
1089                        }
1090                    }
1091                    if ( !$fields and !@$bases ) {
1092                        die ("No schema and no Class::Tangram "
1093                             ."superclass for $class; define "
1094                             ."${class}::fields!");
1095                    }
1096                }
1097            }
1098    
1099            # if this is an abstract type, do not allow it to be
1100            # instantiated
1101            if ($abstract) {
1102                $abstract{$class} = 1;
1103            }
1104    
1105            # If there are any base classes, import them first so that the
1106            # check, cleaners and init_defaults can be inherited
1107            if (defined $bases) {
1108                (ref $bases eq "ARRAY")
1109                    or die "bases not an array ref for $class";
1110    
1111                # record bases of current class for later retrieval via Run-time type information
1112                $bases{$class} = $bases;
1113    
1114                # Note that the order of your bases is significant, that
1115                # is if you are using multiple iheritance then the later
1116                # classes override the earlier ones.
1117                for my $super ( @$bases ) {
1118                    import_schema $super unless (exists $check{$super});
1119    
1120                    # copy each of the per-class configuration hashes to
1121                    # this class as defaults.
1122                    my ($k, $v);
1123    
1124                    # FIXME - this repetition of code is getting silly :)
1125                    $types{$class}->{$k} = $v
1126                        while (($k, $v) = each %{ $types{$super} } );
1127                    $check{$class}->{$k} = $v
1128                        while (($k, $v) = each %{ $check{$super} } );
1129                    $cleaners{$class}->{$k} = $v
1130                        while (($k, $v) = each %{ $cleaners{$super} } );
1131                    $attribute_options{$class}->{$k} = $v
1132                        while (($k, $v) = each %{ $attribute_options{$super} } );
1133                    $init_defaults{$class}->{$k} = $v
1134                        while (($k, $v) = each %{ $init_defaults{$super} } );
1135                    $required_attributes{$class}->{$k} = $v
1136                        while (($k, $v) = each %{ $required_attributes{$super} } );
1137                }
1138            }
1139    
1140            # iterate over each of the *types* of fields (string, int, ref, etc.)
1141            while (my ($type, $v) = each %$fields) {
1142                if (ref $v eq "ARRAY") {
1143                    $v = { map { $_, undef } @$v };
1144                }
1145                my $def = $defaults{$type};
1146    
1147                # iterate each of the *attributes* of a particular type
1148                while (my ($attribute, $options) = each %$v) {
1149    
1150                    # this is what we are finding out about each attribute
1151                    # $type is already set
1152                    my ($default, $check_func, $required, $cleaner);
1153                    # set defaults from what they give
1154                    $options ||= {};
1155                    if (ref $options eq "HASH" or
1156                        UNIVERSAL::isa($options, 'Tangram::Type')) {
1157                        ($check_func, $default, $required, $cleaner)
1158                            = @{$options}{qw(check_func init_default
1159                                             required destroy_func)};
1160                    }
1161    
1162                    # Fill their settings with info from defaults
1163                    if (ref $def eq "HASH") {
1164    
1165                        # try to magically parse their options
1166                        if ( $def->{parse} and !($check_func and $cleaner) ) {
1167                            my @a = $def->{parse}->($attribute, $options);
1168                            $check_func ||= $a[0];
1169                            $cleaner ||= $a[1];
1170                        }
1171    
1172                        # fall back to defaults for this class
1173                        $check_func ||= $def->{check_func};
1174                        $cleaner ||= $def->{destroy_func};
1175                        $default = $def->{init_default} unless defined $default;
1176                    }
1177    
1178                    # everything must be checked!
1179                    die "No check function for ${class}\->$attribute (type $type)"
1180                        unless (ref $check_func eq "CODE");
1181    
1182                    $types{$class}->{$attribute} = $type;
1183                    $check{$class}->{$attribute} = $check_func;
1184                    {
1185                        no strict "refs";
1186                        local ($^W) = 0;
1187    
1188                        # build an appropriate "get_attribute" method, and
1189                        # define other per-type methods
1190                        my ($get_closure, $set_closure);
1191    
1192                        # implement with closures for speed
1193                        if ( $type =~ m/i?set/ ) {
1194    
1195                            # GET_$attribute (Set::Object)
1196                            $get_closure = sub {
1197                                my $self = shift;
1198                                if ( !defined $self->{$attribute} ) {
1199                                    $self->{$attribute} = Set::Object->new();
1200                                }
1201                                my $set = $self->{$attribute};
1202                                ( wantarray ? $set->members : $set )
1203                            };
1204    
1205                            # and add a whole load of other functions too
1206                            for my $set_method (qw(includes insert size clear
1207                                                   remove)) {
1208    
1209                                # ${attribute}_includes, etc
1210                                my $set_method_closure = sub {
1211                                    my $self = shift;
1212                                    $self->{$attribute} = Set::Object->new()
1213                                        unless defined $self->{$attribute};
1214                                    return $self->{$attribute}->$set_method(@_);
1215                                };
1216                                *{$class."::${attribute}_$set_method"} =
1217                                    $set_method_closure unless
1218                                        (defined &{$class."::${attribute}_$set_method"});
1219                            }
1220    
1221                        } elsif ( $type =~ m/i?array/ ) {
1222    
1223                            # GET_$attribute (array)
1224                            # allow array slices, and return whole array
1225                            # in list context
1226                            $get_closure = sub {
1227                                my $array = ($_[0]->{$attribute} ||= []);
1228                                shift;
1229                                if ( @_ ) {
1230                                    @{$array}[@_];
1231                                } else {
1232                                    ( wantarray ? @{ $array } : $array )
1233                                }
1234                            };
1235    
1236                        } elsif ( $type =~ m/i?hash/ ) {
1237                            # GET_$attribute (hash)
1238                            # allow hash slices, and return whole hash in
1239                            # list context
1240                            $get_closure = sub {
1241                                my $hash = ($_[0]->{$attribute} ||= {});
1242                                shift;
1243                                if ( @_ ) {
1244                                    @{$hash}{@_}
1245                                } else {
1246                                    ( wantarray ? %{ $hash } : $hash );
1247                                }
1248                            };
1249                        } else {
1250                            # GET_$attribute (scalar)
1251                            # return value only
1252                            $get_closure = sub { $_[0]->{$attribute}; };
1253                        }
1254    
1255                        *{$class."::get_$attribute"} = $get_closure
1256                            unless (defined &{$class."::get_$attribute"});
1257    
1258                        # SET_$attribute (all)
1259                        my $checkit = \$check{$class}->{$attribute};
1260    
1261                        # required hack for strings - duplicate the code
1262                        # to avoid the following string comparison for
1263                        # every set
1264                        if ( $type eq "string" ) {
1265                            $set_closure = sub {
1266                                my $self = shift;
1267                                my $value = shift;
1268                                eval {
1269                                    if ( defined $value and length $value ) {
1270                                        ${$checkit}->(\$value);
1271                                    } elsif ( $required ) {
1272                                        die "value is required"
1273                                    } elsif ( defined $required ) {
1274                                        die "value must be defined"
1275                                            unless defined $value;
1276                                    }
1277                                };
1278                                $@ && croak("value failed type check - ${class}->"
1279                                            ."set_$attribute('$value') ($@)");
1280                                $self->{$attribute} = $value;
1281                            };
1282                        } else {
1283                            $set_closure = sub {
1284                                my $self = shift;
1285                                my $value = shift;
1286                                eval {
1287                                    if ( $value ) {
1288                                        ${$checkit}->(\$value);
1289                                    } elsif ( $required ) {
1290                                        die "value is required"
1291                                    } elsif ( defined $required ) {
1292                                        die "value must be defined"
1293                                            unless defined $value;
1294                                    }
1295                                };
1296                                $@ && croak("value failed type check - ${class}->"
1297                                        ."set_$attribute('$value') ($@)");
1298                                $self->{$attribute} = $value;
1299                            };
1300                        }
1301    
1302                        # now export them into the caller's namespace
1303                        my ($getter, $setter)
1304                            = ("get_$attribute", "set_$attribute");
1305                        *{$class."::$getter"} = $get_closure
1306                            unless defined &{$class."::$getter"};
1307                        *{$class."::$setter"} = $set_closure
1308                            unless defined &{$class."::$setter"};
1309    
1310                        *{$class."::$attribute"} = sub {
1311                            my $self = shift;
1312                            if ( @_ ) {
1313                                warn("The OO Police say change your call "
1314                                     ."to ->set_$attribute") if ($^W);
1315                                #goto $set_closure;  # NO!  BAD!! :-)
1316                                return $self->$setter(@_);
1317                            } else {
1318                                return $self->$getter(@_);
1319                                #goto $get_closure;
1320                            }
1321                        } unless defined &{$class."::$attribute"};
1322                    }
1323    
1324                    $cleaners{$class}->{$attribute} = $cleaner
1325                        if (defined $cleaner);
1326                    $init_defaults{$class}->{$attribute} = $default
1327                        if (defined $default);
1328                    $required_attributes{$class}->{$attribute} = $required
1329                        if (defined $required);
1330                    $attribute_options{$class}->{$attribute} =
1331                        ( $options || {} );
1332                }
1333            }
1334        };
1335    
1336        $@ && die "$@ while trying to import schema for $class";
1337    }
1338    
1339    =back
1340    
1341    =head2 Run-time type information
1342    
1343    It is possible to access the data structures that Class::Tangram uses
1344    internally to verify attributes, create objects and so on.
1345    
1346    This should be considered a B<HIGHLY EXPERIMENTAL> interface to
1347    B<INTERNALS> of Class::Tangram.
1348    
1349    Class::Tangram keeps seven internal hashes:
1350    
1351    =over
1352    
1353    =item C<%types>
1354    
1355    C<$types{$class}-E<gt>{$attribute}> is the tangram type of each attribute,
1356    ie "ref", "iset", etc.  See L<Tangram::Type>.
1357    
1358    =item C<%attribute_options>
1359    
1360    C<$attribute_options{$class}-E<gt>{$attribute}> is the options hash
1361    for a given attribute.
1362    
1363    =item C<%required_attributes>
1364    
1365    C<$required_attributes{$class}-E<gt>{$attribute}> is the 'required'
1366    option setting for a given attribute.
1367    
1368    =item C<%check>
1369    
1370    C<$check{$class}-E<gt>{$attribute}> is a function that will be passed
1371    a reference to the value to be checked and either throw an exception
1372    (die) or return true.
1373    
1374    =item C<%cleaners>
1375    
1376    C<$attribute_options{$class}-E<gt>{$attribute}> is a reference to a
1377    destructor function for that attribute.  It is called as an object
1378    method on the object being destroyed, and should ensure that any
1379    circular references that this object is involved in get cleared.
1380    
1381    =item C<%abstract>
1382    
1383    C<$abstract-E<gt>{$class}> is set if the class is abstract
1384    
1385    =item C<%init_defaults>
1386    
1387    C<$init_defaults{$class}-E<gt>{$attribute}> represents what an
1388    attribute is set to automatically if it is not specified when an
1389    object is created. If this is a scalar value, the attribute is set to
1390    the value. If it is a function, then that function is called (as a
1391    method) and should return the value to be placed into that attribute.
1392    If it is a hash ref or an array ref, then that structure is COPIED in
1393    to the new object.  If you don't want that, you can do something like
1394    this:
1395    
1396       [...]
1397        flat_hash => {
1398            attribute => {
1399                init_default => sub { { key => "value" } },
1400            },
1401        },
1402       [...]
1403    
1404    Now, every new object will share the same hash for that attribute.
1405    
1406  =back  =back
1407    
1408    There are currently four functions that allow you to access parts of
1409    this information.
1410    
1411    =over
1412    
1413    =item Class::Tangram::attribute_options($class)
1414    
1415    Returns a hash ref to a data structure from attribute names to the
1416    option hash for that attribute.
1417    
1418    =cut
1419    
1420    sub attribute_options($) {
1421        my $class = shift;
1422        return $attribute_options{$class};
1423    }
1424    
1425    =item Class::Tangram::attribute_types($class)
1426    
1427    Returns a hash ref from attribute names to the tangram type for that
1428    attribute.
1429    
1430    =cut
1431    
1432    sub attribute_types($) {
1433        my $class = shift;
1434        return $types{$class};
1435    }
1436    
1437    =item Class::Tangram::required_attributes($class)
1438    
1439    Returns a hash ref from attribute names to the 'required' option setting for
1440    that attribute.  May also be called as a method, as in
1441    C<$instance-E<gt>required_attributes>.
1442    
1443    =cut
1444    
1445    sub required_attributes($) {
1446        my $class = ref $_[0] || $_[0];
1447        return $required_attributes{$class};
1448    }
1449    
1450    =item Class::Tangram::init_defaults($class)
1451    
1452    Returns a hash ref from attribute names to the default intial values for
1453    that attribute.  May also be called as a method, as in
1454    C<$instance-E<gt>init_defaults>.
1455    
1456    =cut
1457    
1458    sub init_defaults($) {
1459        my $class = ref $_[0] || $_[0];
1460        return $init_defaults{$class};
1461    }
1462    
1463    =item Class::Tangram::known_classes
1464    
1465    This function returns a list of all the classes that have had their
1466    object schema imported by Class::Tangram.
1467    
1468    =cut
1469    
1470    sub known_classes {
1471        return keys %types;
1472    }
1473    
1474    =item Class::Tangram::is_abstract($class)
1475    
1476    This function returns true if the supplied class is abstract.
1477    
1478    =cut
1479    
1480    sub is_abstract {
1481        my $class = shift;
1482        $class eq "Class::Tangram" && ($class = shift);
1483    
1484        exists $cleaners{$class} or import_schema($class);
1485    }
1486    
1487    =item Class::Tangram::class_bases($class)
1488    
1489    Returns an array ref of class names the given class inherits from.
1490    
1491    =cut
1492    
1493    sub class_bases($) {
1494        my $class = shift;
1495        return $bases{$class};
1496    }
1497    
1498    =item Class->set_init_default(attribute => $value);
1499    
1500    Sets the default value on an attribute for newly created "Class"
1501    objects, as if it had been declared with init_default.  Can be called
1502    as a class or an instance method.
1503    
1504    =cut
1505    
1506    sub set_init_default {
1507        my $invocant = shift;
1508        my $class = ref $invocant || $invocant;
1509    
1510        exists $init_defaults{$class} or import_schema($class);
1511    
1512        while ( my ($attribute, $value) = splice @_, 0, 2) {
1513            $init_defaults{$class}->{$attribute} = $value;
1514        }
1515    }
1516    
1517    =back
1518    
1519    =cut
1520    
1521    # a little embedded package
1522    
1523    package Tangram::Transient;
1524    
1525    BEGIN {
1526        eval "use base qw(Tangram::Type)";
1527        if ( $@ ) {
1528            # no tangram
1529        } else {
1530            $Tangram::Schema::TYPES{transient} = bless {}, __PACKAGE__;
1531        }
1532    }
1533    
1534    sub coldefs { }
1535    
1536    sub get_exporter { }
1537    sub get_importer { }
1538    
1539    sub get_import_cols {
1540    #    print "Get_import_cols:" , Dumper \@_;
1541        return ();
1542    }
1543    
1544  =head1 SEE ALSO  =head1 SEE ALSO
1545    
1546  L<Tangram::Schema>  L<Tangram::Schema>
# Line 1013  B<A guided tour of Tangram, by Sound Obj Line 1549  B<A guided tour of Tangram, by Sound Obj
1549    
1550   http://www.soundobjectlogic.com/tangram/guided_tour/fs.html   http://www.soundobjectlogic.com/tangram/guided_tour/fs.html
1551    
1552  =head1 BUGS/TODO  =head1 DEPENDENCIES
1553    
1554  More datetime types.  I originally avoided the DMDateTime type because  The following modules are required to be installed to use
1555  Date::Manip is self-admittedly the most bloated module on CPAN, and I  Class::Tangram:
 didn't want to be seen encouraging it.  Then I found out about  
 autosplit :-}.  
1556    
1557  More AUTOLOAD methods, in particular for container types such as     Set::Object => 1.02
1558  array, hash, etc.     Pod::Constants => 0.11
1559       Test::Simple => 0.18
1560       Date::Manip => 5.21
1561    
1562  This documentation should be easy enough for a fool to understand.  Test::Simple and Date::Manip are only required to run the test suite.
1563    
1564    If you find Class::Tangram passes the test suite with earlier versions
1565    of the above modules, please send me an e-mail.
1566    
1567    =head2 MODULE RELEASE
1568    
1569    This is Class::Tangram version 1.13.
1570    
1571    =head1 BUGS/TODO
1572    
1573  There should be more functions for breaking loops; in particular, a  There should be more functions for breaking loops; in particular, a
1574  standard function called drop_refs($obj), which replaces references to  standard function called C<drop_refs($obj)>, which replaces references
1575  $obj with the appropriate Tangram::RefOnDemand so that an object can  to $obj with the appropriate C<Tangram::RefOnDemand> object so that an
1576  be unloaded via Tangram::Storage->unload() and actually have a hope of  object can be unloaded via C<Tangram::Storage->unload()> and actually
1577  being reclaimed.  Another function that would be handy would be a  have a hope of being reclaimed.  Another function that would be handy
1578  deep "mark" operation for mark & sweep garbage collection.  would be a deep "mark" operation for manual mark & sweep garbage
1579    collection.
1580    
1581    Need to think about writing some functions using C<Inline> for speed.
1582    One of these days...
1583    
1584    Allow C<init_default> values to be set in a default import function?
1585    
1586    ie
1587    
1588      use MyClassTangramObject -defaults => { foo => "bar" };
1589    
1590  =head1 AUTHOR  =head1 AUTHOR
1591    
1592  Sam Vilain, <sam@vilain.net>  Sam Vilain, <sam@vilain.net>
1593    
1594    =head2 CREDITS
1595    
1596     # Some modifications
1597     # Copyright 2001 Micro Sharp Technologies, Inc., Vancouver, WA, USA
1598     # Author: Karl M. Hegbloom <karlheg@microsharp.com>
1599     # Perl Artistic Licence.
1600    
1601    Many thanks to Charles Owens and David Wheeler for their feedback,
1602    ideas, patches and bug testing.
1603    
1604  =cut  =cut
1605    
1606  69;  69;
1607    
1608    __END__
1609    
1610     # From old SYNOPSIS, I decided it was too long.  A lot of
1611     # the information here needs to be re-integrated into the
1612     # POD.
1613    
1614     package Project;
1615    
1616     # here's where we build the individual object schemas into
1617     # a Tangram::Schema object, which the Tangram::Storage
1618     # class uses to know which tables and columns to find
1619     # objects.
1620     use Tangram::Schema;
1621    
1622     # TIMTOWTDI - this is the condensed manpage version :)
1623     my $dbschema = Tangram::Schema->new
1624         ({ classes =>
1625           [ 'Orange'   => { fields => $Orange::fields },
1626             'MyObject' => { fields => $MyObject::schema }, ]});
1627    
1628     sub schema { $dbschema };
1629    
1630     package main;
1631    
1632     # See Tangram::Relational for instructions on using
1633     # "deploy" to create the database this connects to.  You
1634     # only have to do this if you want to write the objects to
1635     # a database.
1636     use Tangram::Relational;
1637     my ($dsn, $u, $p);
1638     my $storage = Tangram::Relational->connect
1639                       (Project->schema, $dsn, $u, $p);
1640    
1641     # Create an orange
1642     my $orange = Orange->new(
1643                              juiciness => 8,
1644                              type => 'Florida',
1645                              tag => '',  # required
1646                             );
1647    
1648     # Store it
1649     $storage->insert($orange);
1650    
1651     # This is how you get values out of the objects
1652     my $juiciness = $orange->juiciness;
1653    
1654     # a "ref" must be set to a blessed object, any object
1655     my $grower = bless { name => "Joe" }, "Farmer";
1656     $orange->set_grower ($grower);
1657    
1658     # these are all illegal - type checking is fairly strict
1659     my $orange = eval { Orange->new; };         print $@;
1660     eval { $orange->set_juiciness ("Yum"); };   print $@;
1661     eval { $orange->set_segments (31); };       print $@;
1662     eval { $orange->set_grower ("Mr. Nice"); }; print $@;
1663    
1664     # Demonstrate some "required" functionality
1665     eval { $orange->set_type (''); };           print $@;
1666     eval { $orange->set_type (undef); };        print $@;
1667     eval { $orange->set_tag (undef); };         print $@;
1668    
1669     # this works too, but is slower
1670     $orange->get( "juiciness" );
1671     $orange->set( juiciness => 123,
1672                   segments  => 17 );
1673    
1674     # Re-configure init_default - make each new orange have a
1675     # random juiciness
1676     $orange->set_init_default( juiciness => sub { int(rand(45)) } );

Legend:
Removed from v.1.1  
changed lines
  Added in v.1.4

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed