File Coverage

blib/lib/ORM/Metaprop.pm
Criterion Covered Total %
statement 100 191 52.3
branch 23 66 34.8
condition 9 18 50.0
subroutine 18 25 72.0
pod 0 1 0.0
total 150 301 49.8


line stmt bran cond sub pod time code
1             #
2             # DESCRIPTION
3             # PerlORM - Object relational mapper (ORM) for Perl. PerlORM is Perl
4             # library that implements object-relational mapping. Its features are
5             # much similar to those of Java's Hibernate library, but interface is
6             # much different and easier to use.
7             #
8             # AUTHOR
9             # Alexey V. Akimov
10             #
11             # COPYRIGHT
12             # Copyright (C) 2005-2006 Alexey V. Akimov
13             #
14             # This library is free software; you can redistribute it and/or
15             # modify it under the terms of the GNU Lesser General Public
16             # License as published by the Free Software Foundation; either
17             # version 2.1 of the License, or (at your option) any later version.
18             #
19             # This library is distributed in the hope that it will be useful,
20             # but WITHOUT ANY WARRANTY; without even the implied warranty of
21             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22             # Lesser General Public License for more details.
23             #
24             # You should have received a copy of the GNU Lesser General Public
25             # License along with this library; if not, write to the Free Software
26             # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27             #
28              
29             package ORM::Metaprop;
30              
31             $VERSION=0.81;
32              
33 5     5   29 use Carp;
  5         12  
  5         1182  
34 5     5   30 use ORM;
  5         10  
  5         99  
35 5     5   3245 use ORM::Tjoin;
  5         14  
  5         167  
36 5     5   41 use base 'ORM::Expr';
  5         7  
  5         13674  
37              
38             my %CLASS2METACLASS = ();
39             my %METACLASS2CLASS = ();
40              
41             ##
42             ## CONSTRUCTORS
43             ##
44              
45             ## use: $prop = $class->new( expr=>ORM::Expr );
46             ##
47             sub new
48             {
49 0     0 0 0 my $class = shift;
50 0         0 my %arg = @_;
51 0         0 my $self;
52              
53 0 0       0 if( $class eq 'ORM::Metaprop' )
    0          
54             {
55 0         0 $self = $arg{expr};
56             }
57             elsif( $arg{expr} )
58             {
59 0         0 $self =
60             {
61             expr => $arg{expr},
62             tjoin => $arg{expr}->_tjoin->copy,
63             prop_class => $class->_metaclass2class( $class ),
64             };
65              
66 0         0 bless $self, $class;
67             }
68              
69 0         0 return $self;
70             }
71              
72             ## use: $prop = $class->_new
73             ## (
74             ## prop => STRING,
75             ## prop_class => STRING,
76             ## )
77             ##
78             ## prop_class:
79             ## The class that 'prop' property belongs to.
80             ##
81             ## prop:
82             ##
83             ## prop =~ ( '->' DIRECT_PROPERTY | '-<' REVERSE_PROPERTY )+
84             ## REVERSE_PROPERTY =~ REFERRING_CLASS '.' CLASS_PROPERTY '.' ALIAS
85             ##
86             ## DIRECT_PROPERTY - property of the target class
87             ## REVERSE_PROPERTY - property of third class that refers to target class
88             ## REFERRING_CLASS - class that refers to target class by one of its properties
89             ## CLASS_PROPERTY - property name of the instance of the referring class
90             ## ALIAS - alpha-numeric string, aliasing allows to use different
91             ## referring objects of the same type
92             ##
93             sub _new
94             {
95 7     7   16 my $class = shift;
96 7         24 my %arg = @_;
97 7         36 my @prop = $class->_parse_prop_str( str=>$arg{prop} );
98 7         15 my $self;
99             my $error;
100              
101 7 50       34 if( $prop[0]{type} eq '>' )
102             {
103 7         30 $self = $class->_new_flat( class=>$arg{prop_class}, prop=>$prop[0]{name} );
104             }
105              
106 7 50       1078 if( defined $self )
107             {
108 7         26 for( my $i=1; $i<@prop; $i++ )
109             {
110 0 0       0 if( $prop[$i]{type} eq '>' )
111             {
112 0 0       0 unless( $self->_expand( prop=>$prop[$i]{name} ) )
113             {
114 0         0 $self = undef;
115 0         0 last;
116             }
117             }
118             else
119             {
120 0         0 $self = undef;
121 0         0 last;
122             }
123             }
124             }
125              
126 7         40 return $self;
127             }
128              
129             ## use: $prop = $class->_new_flat
130             ## (
131             ## class => STRING,
132             ## prop => STRING||undef,
133             ## )
134             ##
135             ## class:
136             ## The class that 'prop' property belongs to.
137             ##
138             ## prop:
139             ## Direct property of the class.
140             ## If this argument is omitted then 'id' is assumed.
141             ##
142             sub _new_flat
143             {
144 21     21   37 my $class = shift;
145 21         104 my %arg = @_;
146 21         31 my $self;
147              
148 21 50 66     112 if( ! $arg{prop} || $arg{class}->_has_prop( $arg{prop} ) )
149             {
150 21         127 $self->{prop} = $arg{prop};
151 21 100       93 $self->{prop_class} = $arg{prop} ? $arg{class}->_prop_class( $arg{prop} ) : $arg{class};
152 21 100       128 $self->{prop_ref_class} = $arg{prop} ? $arg{class}->_prop_is_ref( $arg{prop} ) : $arg{class};
153 21         124 $self->{last_tjoin} = ORM::Tjoin->new( class=>$arg{class}, prop=>$arg{prop} );
154 21         70 $self->{tjoin} = $self->{last_tjoin};
155              
156 21         51 bless $self, $class;
157 21         67 $self->_rebless;
158             }
159             else
160             {
161 0         0 croak "'$arg{prop}' is neither a property of '$arg{class}' nor described in '$class' or parents"
162             }
163              
164 21         168 return $self;
165             }
166              
167             sub _copy
168             {
169 26     26   31 my $self = shift;
170 26         29 my $copy;
171              
172 26 50       64 if( $self->_calculated )
173             {
174 0         0 $copy =
175             {
176             expr => $self->{expr},
177             tjoin => $self->{tjoin}->copy,
178             prop_class => $self->{prop_class},
179             };
180             }
181             else
182             {
183 26         120 $copy =
184             {
185             incomplete => $self->{incomplete},
186             prop => $self->{prop},
187             tjoin => $self->{tjoin}->copy,
188             prop_class => $self->{prop_class},
189             prop_ref_class => $self->{prop_ref_class},
190             };
191 26         109 $copy->{last_tjoin} = $copy->{tjoin}->corresponding_node( $self->{tjoin} );
192             }
193              
194 26         77 return bless $copy, ref $self;
195             }
196              
197             ##
198             ## PROPERTIES
199             ##
200              
201             sub _prop
202             {
203 26     26   42 my $self = shift;
204 26         69 my $copy = $self->_copy;
205              
206 26         84 $copy->_expand( @_ );
207 26         178 return $copy;
208             }
209              
210             sub _rev
211             {
212 0     0   0 my $self = shift;
213 0         0 my $copy = $self->_copy;
214              
215 0         0 $copy->_rev_expand( @_ );
216 0         0 return $copy;
217             }
218              
219             sub _arb
220             {
221 0     0   0 my $self = shift;
222 0         0 my $copy = $self->_copy;
223              
224 0         0 $copy->_arb_expand( @_ );
225 0         0 return $copy;
226             }
227              
228             sub AUTOLOAD
229             {
230 11     11   21 my $self = shift;
231              
232 11 50       31 if( ref $self )
233             {
234 11         89 $self->_prop( substr( $AUTOLOAD, rindex( $AUTOLOAD, '::' )+2 ), @_ );
235             }
236             else
237             {
238 0         0 croak "Undefined static method called: $AUTOLOAD";
239             }
240             }
241              
242 58     58   173 sub _calculated { shift->{expr}; }
243 20     20   81 sub _tjoin { shift->{tjoin}; }
244 8     8   95 sub _prop_ref_class { shift->{prop_ref_class}; }
245 2     2   8 sub _prop_class { shift->{prop_class}; }
246              
247             ## use: $sql_str = $prop->_sql_str( tjoin => ORM::Tjoin )
248             ##
249             sub _sql_str
250             {
251 32     32   46 my $self = shift;
252 32         87 my %arg = @_;
253 32         42 my $str;
254              
255 32 50       66 if( $self->_calculated )
256             {
257 0         0 $str = $self->_calculated->_sql_str( tjoin=>$arg{tjoin} );
258             }
259             else
260             {
261 32         134 my $node = $arg{tjoin}->corresponding_node( $self->{tjoin} );
262 32   33     225 $str = $node && $node->full_field_name( $self->{prop}||'id' );
263             }
264              
265 32         249 return $str;
266             }
267              
268             ##
269             ## METHODS
270             ##
271              
272             ## use: $metaprop->_expand( STRING );
273             ##
274             ## Extends $metaprop meta-property to be 'prop' meta-property of $metaprop.
275             ##
276             sub _expand
277             {
278 26     26   30 my $self = shift;
279 26         53 my $prop = shift;
280 26         42 my %arg = @_;
281              
282 26 50 66     118 if( $prop eq 'class' && $self->_prop_ref_class && $self->_prop_ref_class->_is_sealed )
      66        
283             {
284 0         0 my $const = ORM::Const->new( $self->_prop_ref_class );
285 0         0 %{$self} = %{$const};
  0         0  
  0         0  
286 0         0 bless $self, ref $const;
287             }
288             else
289             {
290 26 50       165 if( !$self->{prop_ref_class} )
    50          
291             {
292 0         0 croak "Class '$self->{prop_class}' is not expandable";
293             }
294             elsif( !$self->{prop_ref_class}->_has_prop( $prop ) )
295             {
296 0         0 croak "Class '$self->{prop_ref_class}' has no property '$prop'";
297             }
298             else
299             {
300 26 100       294 if( $self->{prop} )
301             {
302 7         26 my $tjoin = ORM::Tjoin->new( class=>$self->{prop_ref_class}, prop=>$prop );
303 7         31 $self->{last_tjoin}->link( $self->{prop} => $tjoin );
304 7         18 $self->{last_tjoin} = $tjoin;
305             }
306             else
307             {
308 19         72 $self->{last_tjoin}->use_prop( $prop );
309             }
310              
311 26         47 my $new_class;
312             my $new_ref_class;
313              
314 26 50       61 if( $arg{cast} )
315             {
316 0 0       0 if( UNIVERSAL::isa( $arg{cast}, $self->{prop_ref_class}->_prop_class( $prop ) ) )
317             {
318 0         0 $new_class = $arg{cast};
319 0         0 $new_ref_class = $arg{cast};
320             }
321             else
322             {
323 0         0 croak "Can't cast class '".$self->{prop_ref_class}->_prop_class( $prop )."' to '$arg{cast}'";
324             }
325             }
326             else
327             {
328 26         131 $new_class = $self->{prop_ref_class}->_prop_class( $prop );
329 26         371 $new_ref_class = $self->{prop_ref_class}->_prop_is_ref( $prop );
330             }
331              
332 26         115 $self->{prop} = $prop;
333 26         42 $self->{prop_class} = $new_class;
334 26         45 $self->{prop_ref_class} = $new_ref_class;
335              
336 26         60 $self->_rebless;
337             }
338             }
339             }
340              
341             sub _rev_expand
342             {
343 0     0   0 my $self = shift;
344 0         0 my $rev_class = shift;
345 0         0 my $rev_prop = shift;
346 0         0 my $cond = shift;
347              
348 0 0       0 if( !$self->{prop_ref_class} )
    0          
349             {
350 0         0 croak "Class '$self->{prop_class}' is not expandable";
351             }
352             elsif( !$self->{prop_ref_class}->_has_rev_ref( $rev_class, $rev_prop ) )
353             {
354 0         0 croak "There is no property '$rev_prop' of class '$rev_class' referring to '$self->{prop_class}'";
355             }
356             else
357             {
358 0         0 $self->_arb_expand( 'id' => $rev_class, $rev_prop, $cond );
359             }
360             }
361              
362             ## use: $node->_arb_expand( $prop => $exp_class, $exp_prop, $additional_condition );
363             ##
364             sub _arb_expand
365             {
366 0     0   0 my $self = shift;
367 0         0 my $prop = shift;
368 0         0 my $exp_class = shift;
369 0         0 my $exp_prop = shift;
370 0         0 my $cond = shift;
371              
372 0 0       0 if( !$self->{prop_ref_class} )
    0          
    0          
373             {
374 0         0 croak "Class '$self->{prop_class}' is not expandable";
375             }
376             elsif( !$self->{prop_ref_class}->_has_prop( $prop ) )
377             {
378 0         0 croak "Class '$self->{prop_ref_class}' has no property '$prop'";
379             }
380             elsif( !$exp_class->_has_prop( $exp_prop ) )
381             {
382 0         0 croak "Target class '$exp_class' has no property '$exp_prop'";
383             }
384             else
385             {
386 0 0       0 if( $self->{prop} )
387             {
388 0         0 my $tjoin = ORM::Tjoin->new( class=>$self->{prop_ref_class} );
389 0         0 $self->{last_tjoin}->link( $self->{prop} => $tjoin );
390 0         0 $self->{last_tjoin} = $tjoin;
391 0         0 $self->{prop} = undef;
392             }
393              
394 0         0 my $tjoin = ORM::Tjoin->new( class=>$exp_class, left_prop=>$exp_prop, cond=>$cond );
395 0         0 $self->{last_tjoin}->link( $prop => $tjoin );
396 0         0 $self->{last_tjoin} = $tjoin;
397              
398 0         0 $self->{prop_class} = $exp_class;
399 0         0 $self->{prop_ref_class} = $exp_class;
400              
401 0         0 $self->_rebless;
402             }
403             }
404              
405             ## use: @prop = $prop->_parse_prop_str( str=>STRING );
406             ##
407             ## Each element of resulting array is hash, containing fields:
408             ##
409             ## type: '>' - direct or '<' - reverse property
410             ## name: name of the property
411             ## class: (only makes sence for reverse properties) referring class
412             ## alias: (only makes sence for reverse properties) alias
413             ##
414             sub _parse_prop_str
415             {
416 7     7   11 my $self = shift;
417 7         18 my %arg = @_;
418 7         17 my $str = $arg{str};
419 7         11 my @struct;
420              
421             ## Parse prop string
422 7 50       23 if( substr( $str, 0, 1 ) eq '-' )
423             {
424 0         0 $str = substr $str, 1;
425 0         0 @struct = split /\-/, $str;
426 0         0 for( my $i=0; $i<@struct; $i++ )
427             {
428 0         0 my %prop;
429              
430 0         0 %prop = ();
431 0         0 $prop{type} = substr $struct[$i], 0, 1;
432              
433 0 0       0 if( $prop{type} eq '>' )
    0          
434             {
435 0         0 $prop{name} = substr $struct[$i], 1;
436             }
437             elsif( $prop{type} eq '<' )
438             {
439 0         0 ( $prop{class}, $prop{name}, $prop{alias} ) =
440             split /\./, substr $struct[$i], 1;
441             }
442              
443 0         0 $struct[$i] = \%prop;
444             }
445             }
446             else
447             {
448 7         36 @struct = ( { type=>'>', name=>$str } );
449             }
450              
451 7         32 return @struct;
452             }
453              
454             sub _class2metaclass
455             {
456 3     3   8 my $self = shift;
457 3         4 my $class = shift;
458 3         6 my $meta;
459             my $path;
460              
461 3 50       13 if( exists $CLASS2METACLASS{$class} )
462             {
463 0         0 $meta = $CLASS2METACLASS{$class};
464             }
465             else
466             {
467 3         7 $meta = "ORM::Meta::$class";
468 3         7 $path = $meta.'.pm';
469 3         16 $path =~ s(::)(/)g;
470              
471 3 100 66     413 unless( $INC{$path} || eval "require $meta" )
472             {
473 1         2 $meta = 'ORM::Metaprop';
474             }
475              
476 3         16 $CLASS2METACLASS{$class} = $meta;
477 3         8 $METACLASS2CLASS{$meta} = $class;
478             }
479              
480 3         13 return $meta;
481             }
482              
483             sub _metaclass2class
484             {
485 0     0   0 my $self = shift;
486 0         0 my $meta = shift;
487 0         0 my $class;
488             my $path;
489              
490 0 0       0 if( exists $METACLASS2CLASS{$meta} )
491             {
492 0         0 $class = $METACLASS2CLASS{$meta};
493             }
494             else
495             {
496 0         0 $class = substr $meta, 11;
497 0         0 $path = $class.'.pm';
498 0         0 $path =~ s(::)(/)g;
499              
500 0 0 0     0 if( $INC{$path} || eval "require $class" )
501             {
502 0         0 $CLASS2METACLASS{$class} = $meta;
503 0         0 $METACLASS2CLASS{$meta} = $class;
504             }
505             else
506             {
507 0         0 croak "Can't autoload class '$class'";
508             }
509             }
510              
511 0         0 return $class;
512             }
513              
514             sub _rebless
515             {
516 47     47   68 my $self = shift;
517 47         58 my $class;
518              
519 47 100       296 if( $self->{prop_ref_class} )
    50          
520             {
521 18         109 $class = $self->{prop_ref_class}->metaprop_class;
522             }
523             elsif( $self->{prop_class} )
524             {
525 0         0 $class = $self->_class2metaclass( $self->{prop_class} );
526             }
527             else
528             {
529 29         50 $class = 'ORM::Metaprop';
530             }
531              
532 47         269 bless $self, $class;
533             }
534              
535             sub DESTROY
536 0     0     {
537             }
538              
539             1;