Implement an idea of reducing inline constructors in basic metaclasses
[gitmo/Class-MOP.git] / lib / Class / MOP / Method / Constructor.pm
1
2 package Class::MOP::Method::Constructor;
3
4 use strict;
5 use warnings;
6
7 use Carp         'confess';
8 use Scalar::Util 'blessed', 'weaken', 'looks_like_number';
9
10 our $VERSION   = '0.89';
11 $VERSION = eval $VERSION;
12 our $AUTHORITY = 'cpan:STEVAN';
13
14 use base 'Class::MOP::Method::Inlined';
15
16 sub new {
17     my $class   = shift;
18     my %options = @_;
19
20     (blessed $options{metaclass} && $options{metaclass}->isa('Class::MOP::Class'))
21         || confess "You must pass a metaclass instance if you want to inline"
22             if $options{is_inline};
23
24     ($options{package_name} && $options{name})
25         || confess "You must supply the package_name and name parameters $Class::MOP::Method::UPGRADE_ERROR_TEXT";
26
27     my $self = $class->_new(\%options);
28
29     # we don't want this creating
30     # a cycle in the code, if not
31     # needed
32     weaken($self->{'associated_metaclass'});
33
34     $self->_initialize_body;
35
36     return $self;
37 }
38
39 sub _new {
40     my $class = shift;
41     return Class::MOP::Class->initialize($class)->new_object(@_)
42       if $class ne __PACKAGE__;
43
44     my $params = @_ == 1 ? $_[0] : {@_};
45
46     return bless {
47         # inherited from Class::MOP::Method
48         body                 => $params->{body},
49         # associated_metaclass => $params->{associated_metaclass}, # overriden
50         package_name         => $params->{package_name},
51         name                 => $params->{name},
52         original_method      => $params->{original_method},
53
54         # inherited from Class::MOP::Generated
55         is_inline            => $params->{is_inline} || 0,
56         definition_context   => $params->{definition_context},
57
58         # inherited from Class::MOP::Inlined
59         _expected_method_class => $params->{_expected_method_class},
60
61         # defined in this subclass
62         options              => $params->{options} || {},
63         associated_metaclass => $params->{metaclass},
64     }, $class;
65 }
66
67 ## accessors
68
69 sub options              { (shift)->{'options'}              }
70 sub associated_metaclass { (shift)->{'associated_metaclass'} }
71
72 ## cached values ...
73
74 sub meta_instance {
75     Carp::cluck('The meta_instance method has been made private.'
76         . " The public version is deprecated and will be removed in a future release.\n");
77     shift->_meta_instance;
78 }
79
80 sub _meta_instance {
81     my $self = shift;
82     $self->{'meta_instance'} ||= $self->associated_metaclass->get_meta_instance;
83 }
84
85 sub attributes {
86     Carp::cluck('The attributes method has been made private.'
87         . " The public version is deprecated and will be removed in a future release.\n");
88
89     return shift->_attributes;
90 }
91
92 sub _attributes {
93     my $self = shift;
94     $self->{'attributes'} ||= [ $self->associated_metaclass->get_all_attributes ]
95 }
96
97 ## method
98
99 sub initialize_body {
100     Carp::cluck('The initialize_body method has been made private.'
101         . " The public version is deprecated and will be removed in a future release.\n");
102     shift->_initialize_body;
103 }
104
105 sub _initialize_body {
106     my $self        = shift;
107     my $method_name = '_generate_constructor_method';
108
109     $method_name .= '_inline' if $self->is_inline;
110
111     $self->{'body'} = $self->$method_name;
112 }
113
114 sub generate_constructor_method {
115     Carp::cluck('The generate_constructor_method method has been made private.'
116         . " The public version is deprecated and will be removed in a future release.\n");
117     shift->_generate_constructor_method;
118 }
119
120 sub _generate_constructor_method {
121     return sub { Class::MOP::Class->initialize(shift)->new_object(@_) }
122 }
123
124 sub generate_constructor_method_inline {
125     Carp::cluck('The generate_constructor_method_inline method has been made private.'
126         . " The public version is deprecated and will be removed in a future release.\n");
127     shift->_generate_constructor_method_inline;
128 }
129
130 sub _generate_constructor_method_inline {
131     my $self = shift;
132
133     my $close_over = {};
134
135     my $source = 'sub {';
136     $source .= "\n" . 'my $class = shift;';
137
138     $source .= "\n" . 'return Class::MOP::Class->initialize($class)->new_object(@_)';
139     $source .= "\n" . '    if $class ne \'' . $self->associated_metaclass->name . '\';';
140
141     $source .= "\n" . 'my $params = @_ == 1 ? $_[0] : {@_};';
142
143     $source .= "\n" . 'my $instance = ' . $self->_meta_instance->inline_create_instance('$class');
144     $source .= ";\n" . (join ";\n" => map {
145         $self->_generate_slot_initializer($_, $close_over)
146     } @{ $self->_attributes });
147     $source .= ";\n" . 'return $instance';
148     $source .= ";\n" . '}';
149     warn $source if $self->options->{debug};
150
151     my ( $code, $e ) = $self->_eval_closure(
152         $close_over,
153         $source
154     );
155     confess "Could not eval the constructor :\n\n$source\n\nbecause :\n\n$e" if $e;
156
157     return $code;
158 }
159
160 sub _generate_slot_initializer {
161     my $self  = shift;
162     my $attr  = shift;
163     my $close = shift;
164
165     my $default;
166     if ($attr->has_default) {
167         # NOTE:
168         # default values can either be CODE refs
169         # in which case we need to call them. Or
170         # they can be scalars (strings/numbers)
171         # in which case we can just deal with them
172         # in the code we eval.
173         if ($attr->is_default_a_coderef) {
174             my $idx = @{$close->{'@defaults'}||=[]};
175             push(@{$close->{'@defaults'}}, $attr->default);
176             $default = '$defaults[' . $idx . ']->($instance)';
177         }
178         else {
179             $default = $attr->default;
180             # make sure to quote strings ...
181             unless (looks_like_number($default)) {
182                 $default = "'$default'";
183             }
184         }
185     } elsif( $attr->has_builder ) {
186         $default = '$instance->'.$attr->builder;
187     }
188
189     if ( defined(my $init_arg = $attr->init_arg) ) {
190       return (
191           'if(exists $params->{\'' . $init_arg . '\'}){' . "\n" .
192                 $self->_meta_instance->inline_set_slot_value(
193                     '$instance',
194                     $attr->name,
195                     '$params->{\'' . $init_arg . '\'}' ) . "\n" .
196            '} ' . (!defined $default ? '' : 'else {' . "\n" .
197                 $self->_meta_instance->inline_set_slot_value(
198                     '$instance',
199                     $attr->name,
200                      $default ) . "\n" .
201            '}')
202         );
203     } elsif ( defined $default ) {
204         return (
205             $self->_meta_instance->inline_set_slot_value(
206                 '$instance',
207                 $attr->name,
208                  $default ) . "\n"
209         );
210     } else { return '' }
211 }
212
213 1;
214
215 __END__
216
217 =pod
218
219 =head1 NAME
220
221 Class::MOP::Method::Constructor - Method Meta Object for constructors
222
223 =head1 SYNOPSIS
224
225   use Class::MOP::Method::Constructor;
226
227   my $constructor = Class::MOP::Method::Constructor->new(
228       metaclass => $metaclass,
229       options   => {
230           debug => 1, # this is all for now
231       },
232   );
233
234   # calling the constructor ...
235   $constructor->body->execute($metaclass->name, %params);
236
237 =head1 DESCRIPTION
238
239 This is a subclass of C<Class::MOP::Method> which generates
240 constructor methods.
241
242 =head1 METHODS
243
244 =over 4
245
246 =item B<< Class::MOP::Method::Constructor->new(%options) >>
247
248 This creates a new constructor object. It accepts a hash reference of
249 options.
250
251 =over 8
252
253 =item * metaclass
254
255 This should be a L<Class::MOP::Class> object. It is required.
256
257 =item * name
258
259 The method name (without a package name). This is required.
260
261 =item * package_name
262
263 The package name for the method. This is required.
264
265 =item * is_inline
266
267 This indicates whether or not the constructor should be inlined. This
268 defaults to false.
269
270 =back
271
272 =item B<< $metamethod->is_inline >>
273
274 Returns a boolean indicating whether or not the constructor is
275 inlined.
276
277 =item B<< $metamethod->associated_metaclass >>
278
279 This returns the L<Class::MOP::Class> object for the method.
280
281 =back
282
283 =head1 AUTHORS
284
285 Stevan Little E<lt>stevan@iinteractive.comE<gt>
286
287 =head1 COPYRIGHT AND LICENSE
288
289 Copyright 2006-2009 by Infinity Interactive, Inc.
290
291 L<http://www.iinteractive.com>
292
293 This library is free software; you can redistribute it and/or modify
294 it under the same terms as Perl itself.
295
296 =cut
297