~ubuntu-branches/ubuntu/oneiric/libclass-objecttemplate-perl/oneiric

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package Class::ObjectTemplate;
require Exporter;

use vars qw(@ISA @EXPORT $VERSION $DEBUG);
use Carp;
use strict;
no strict 'refs';

@ISA = qw(Exporter);
@EXPORT = qw(attributes);
$VERSION = 0.7;

$DEBUG = 0; # assign 1 to it to see code generated on the fly

# Create accessor functions
sub attributes {
  my ($pkg) = caller;

  croak "Error: attributes() invoked multiple times"
    if scalar @{"${pkg}::_ATTRIBUTES_"};

  #
  # We must define a constructor for the class, because we must
  # declare the variables used for the free list, $_max_id and
  # @_free. If we don't, we will get compile errors for any class
  # that declares itself a subclass of any Class::ObjectTemplate
  # class
  #
  my $code .= _define_constructor($pkg);

  # _defined_constructor() may have added attributes that we inherited
  # from any superclasses now add the new attributes
  push(@{"${pkg}::_ATTRIBUTES_"},@_);

  # now define any accessor methods
  print STDERR "Creating methods for $pkg\n" if $DEBUG;
  foreach my $attr (@_) {
    print STDERR "  defining method $attr\n" if $DEBUG;
    # If a field name is "color", create a global list in the
    # calling package called @_color
    @{"${pkg}::_$attr"} = ();

    # If the accessor is already present, give a warning
    if (UNIVERSAL::can($pkg,"$attr")) {
      carp "$pkg already has method: $attr";
    } else {
      $code .= _define_accessor ($pkg, $attr);
    }
  }
  eval $code;
  if ($@) {
    die  "ERROR defining constructor and attributes for '$pkg':\n"
       . "\t$@\n"
       . "-----------------------------------------------------"
       . $code;
  }
}

# $obj->set_attributes (name => 'John', age => 23);
# Or, $obj->set_attributes (['name', 'age'], ['John', 23]);
sub set_attributes {
  my $obj = shift;
  my $attr_name;
  if (ref($_[0])) {
    my ($attr_name_list, $attr_value_list) = @_;
    my $i = 0;
    foreach $attr_name (@$attr_name_list) {
      $obj->$attr_name($attr_value_list->[$i++]);
    }
  } else {
    my ($attr_name, $attr_value);
    while (@_) {
      $attr_name = shift;
      $attr_value = shift;
      $obj->$attr_name($attr_value);
    }
  }
}


# @attrs = $obj->get_attributes (qw(name age));
sub get_attributes {
  my $obj = shift;
  my $pkg = ref($obj);
  my (@retval);
  return map {$ {"${pkg}::_$_"}[$$obj]} @_;
}

sub get_attribute_names {
  my $pkg = shift;
  $pkg = ref($pkg) if ref($pkg);
  return @{"${pkg}::_ATTRIBUTES_"};
}

sub set_attribute {
  my ($obj, $attr_name, $attr_value) = @_;
  my ($pkg) = ref($obj);
  return $ {"${pkg}::_$attr_name"}[$$obj] = $attr_value;
}

sub get_attribute {
  my ($obj, $attr_name, $attr_value) = @_;
  my ($pkg) = ref($obj);
  return $ {"${pkg}::_$attr_name"}[$$obj];
}

sub DESTROY {
  # release id back to free list
  my $obj = shift;
  my $pkg = ref($obj);
  my $inst_id = $$obj;

  # Release all the attributes in that row
  my (@attributes) = get_attribute_names($pkg);
  foreach my $attr (@attributes) {
    undef $ {"${pkg}::_$attr"}[$inst_id];
  }

  # The free list is *always* maintained independently by each base
  # class
  push(@{"${pkg}::_free"},$inst_id);
}

sub initialize { }; # dummy method, if subclass doesn't define one.

#################################################################

sub _define_constructor {
  my $pkg = shift;
  my $free = "\@${pkg}::_free";

  # inherit any attributes from our superclasses
  if (defined (@{"${pkg}::ISA"})) {
    foreach my $base_pkg (@{"${pkg}::ISA"}) {
      push (@{"${pkg}::_ATTRIBUTES_"}, get_attribute_names($base_pkg));
    }
  }

  my $code = <<"CODE";
    package $pkg;
    use vars qw(\$_max_id \@_free);
    sub new {
      my \$class = shift;
      my \$inst_id;
      if (scalar $free) {
	\$inst_id = shift($free);
      } else {
	\$inst_id = \$_max_id++;
      }
      my \$obj = bless \\\$inst_id, \$class;
      \$obj->set_attributes(\@_) if \@_;
      my \$rc = \$obj->initialize;
      return undef if \$rc == -1;
      \$obj;
    }

    # Set up the free list, and the ID counter
    \@_free = ();
    \$_max_id = 0;

CODE
  return $code;
}

sub _define_accessor {
  my ($pkg, $attr) = @_;

  # This code creates an accessor method for a given
  # attribute name. This method  returns the attribute value
  # if given no args, and modifies it if given one arg.
  # Either way, it returns the latest value of that attribute

  my $code = <<"CODE";
    package $pkg;
    sub $attr {                                      # Accessor ...
      my \$name = ref(\$_[0]) . "::_$attr";
         \@_ > 1 ? \$name->[\${\$_[0]}] = \$_[1]  # set
                 : \$name->[\${\$_[0]}];          # get
    }
CODE
  return $code;
}

1;
__END__
### =head1 IMPLEMENTATION DETAILS
###
### This section is intended for the maintainers of Class::ObjectTemplate
### and not the users, and this is why it is not include in the POD.
###
### This section was added to describe pieces that were added after
### Sriram\'s original code.
###
### =head2 INHERITANCE
###
### There were some problems with inheritance in the original version
### described by Sriram, with how attribute values were stored, and with
### how the free list was maintained.
###
### Each subclass must define its own constructor, C<new()>. This is why
### B<every> class that subclasses from another must call C<attributes()>
### even if it doesn\'t define any new attributes. If this does not
### happen, then the class will not properly define its attribute list or
### its free list.
###
### Each subclass maintains its own attribute list, stored in the variable
### C<@_ATTRIBUTES_>, and all attributes defined by any superclasses will
### be copied into the subclass attribute lists by the
### _define_constructor() method.
###
### =head2 FREE LIST
###
### Every class maintains two important variables that are used by the
### class constructor method, C<new()> to assign object id\'s to newly
### created objects, $_max_id and @_free. Each subclass maintains its own
### copy of each of these.
###
### =over
###
### =item @_free
###
### Is the free list which tracks scalar values that were previously but
### are now free to be re-assigned to new objects. 
###
###
### =item $_max_id
###
### Tracks the largest object id used. If the free list is empty, then
### C<new()> assigns a brand new object id by incrementing $_max_id.
###
### =back

=head1 NAME

Class::ObjectTemplate - Perl extension for an optimized template
builder base class.

=head1 SYNOPSIS

  package Foo;
  use Class::ObjectTemplate;
  require Exporter;
  @ISA = qw(Class::ObjectTemplate Exporter);

  attributes('one', 'two', 'three');

  # initialize will be called by new()
  sub initialize {
    my $self = shift;
    $self->three(1) unless defined $self->three();
  }

  use Foo;
  $foo = Foo->new();

  # store 27 in the 'one' attribute
  $foo->one(27);

  # check the value in the 'two' attribute
  die "should be undefined" if defined $foo->two();

  # set using the utility method
  $foo->set_attribute('one',27);

  # check using the utility method
  $two = $foo->get_attribute('two');

  # set more than one attribute using the named parameter style
  $foo->set_attributes('one'=>27, 'two'=>42);

  # or using array references
  $foo->set_attributes(['one','two'],[27,42]);

  # get more than one attribute
  @list = $foo->get_attributes('one', 'two');

  # get a list of all attributes known by an object
  @attrs = $foo->get_attribute_names();

  # check that initialize() is called properly
  die "initialize didn't set three()" unless $foo->three();

=head1 DESCRIPTION

Class::ObjectTemplate is a utility class to assist in the building of
other Object Oriented Perl classes.

It was described in detail in the O\'Reilly book, "Advanced Perl
Programming" by Sriram Srinivasam. 

=head2 EXPORT

attributes(@name_list)

This method creates a shared setter and getter methods for every name
in the list. The method also creates the class constructor, C<new()>.

B<WARNING>: This method I<must> be invoked within the module for every
class that inherits from Class::ObjectTemplate, even if that class
defines no attributes. For a class defining no new attributes, it
should invoke C<attributes()> with no arguments.

=head1 AUTHOR

Original code by Sriram Srinivasam.

Fixes and CPAN module by Jason E. Stewart (jason@openinformatics.com)

=head1 SEE ALSO

http://www.oreilly.com/catalog/advperl/

perl(1).

Class::ObjectTemplate::DB

=cut