File Coverage

lib/Badger/Factory.pm
Criterion Covered Total %
statement 135 150 90.0
branch 41 64 64.0
condition 19 37 51.3
subroutine 21 22 95.4
pod 15 16 93.7
total 231 289 79.9


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Factory
4             #
5             # DESCRIPTION
6             # Factory module for loading and instantiating other modules.
7             #
8             # NOTE
9             # This module has grown organically to fit a number of (possibly
10             # conflicting) needs. It needs to be completely refactored, and
11             # probably split into a number of different factory modules. The
12             # TT3 code on which this was originally based had separate base class
13             # factory modules for modules (that just got loaded), objects (that
14             # got loaded and instantiated) and single objects (that got loaded,
15             # created and cached). With hindsight, it was a mistake to try and
16             # cram all that functionality into one module. It should be separated
17             # into a base class module/API and a number of specialised subclasses.
18             #
19             # AUTHOR
20             # Andy Wardley
21             #
22             #========================================================================
23              
24             package Badger::Factory;
25              
26 24     24   635 use Badger::Debug ':dump';
  24         48  
  24         150  
27             use Badger::Class
28 24         373 version => 0.01,
29             debug => 0,
30             base => 'Badger::Prototype Badger::Exporter',
31             import => 'class',
32             utils => 'plural blessed textlike dotid camel_case',
33             words => 'ITEM ITEMS ISA',
34             constants => 'PKG ARRAY HASH REFS ONCE DEFAULT LOADED',
35             constant => {
36             OBJECT => 'object',
37             FOUND => 'found',
38             FOUND_REF => 'found_ref',
39             PATH_SUFFIX => '_PATH',
40             NAMES_SUFFIX => '_NAMES',
41             DEFAULT_SUFFIX => '_DEFAULT',
42             },
43             messages => {
44             no_item => 'No item(s) specified for factory to manage',
45             no_default => 'No default defined for %s factory',
46             bad_ref => 'Invalid reference for %s factory item %s: %s',
47             bad_method => qq{Can't locate object method "%s" via package "%s" at %s line %s},
48 24     24   176 };
  24         65  
49              
50             our $RUNAWAY = 0;
51             our $AUTOLOAD;
52              
53             *init = \&init_factory;
54              
55              
56             sub init_factory {
57 25     25 0 72 my ($self, $config) = @_;
58 25         108 my $class = $self->class;
59 25         73 my ($item, $items, $path, $map, $default);
60              
61             # 'item' and 'items' can be specified as config params or we look for
62             # $ITEM and $ITEMS variables in the current package or those of any
63             # base classes. NOTE: $ITEM and $ITEMS must be in the same package
64 25 100       100 unless ($item = $config->{ item }) {
65 24         196 foreach my $pkg ($class->heritage) {
66 24     24   181 no strict REFS;
  24         58  
  24         999  
67 24     24   138 no warnings ONCE;
  24         50  
  24         26400  
68              
69 24 50       56 if (defined ($item = ${ $pkg.PKG.ITEM })) {
  24         79  
70 24         66 $items = ${ $pkg.PKG.ITEMS };
  24         63  
71 24         69 last;
72             }
73             }
74             }
75 25 50       76 return $self->error_msg('no_item')
76             unless $item;
77              
78             # use 'items' in config, or grokked from $ITEMS, or guess plural
79 25   33     197 $items = $config->{ items } || $items || plural($item);
80              
81 25         62 my $ipath = $item.PATH_SUFFIX;
82 25         57 my $inames = $item.NAMES_SUFFIX;
83 25         64 my $idefault = $item.DEFAULT_SUFFIX;
84              
85             # Merge all XXXX_PATH package vars with any 'xxxx_path' or 'path' config
86             # items. Ditto for XXXX_NAME / 'xxxx_name' / 'aka' and XXXXS/ 'xxxxs'
87              
88 25         140 my @path = @$config{ path => lc $ipath };
89 25         72 my @names = @$config{ names => lc $inames };
90              
91 25         175 $self->{ path } = $class->list_vars(uc $ipath, @path);
92 25         187 $self->{ names } = $class->hash_vars(uc $inames, @names);
93 25         137 $self->{ $items } = $class->hash_vars(uc $items, $config->{ $items });
94 25         76 $self->{ items } = $items;
95 25         61 $self->{ item } = $item;
96 25         72 $self->{ loaded } = { };
97             $self->{ no_cache } = defined $config->{ no_cache } # quick hack
98             ? $config->{ no_cache }
99 25 50 50     205 : $class->any_var('NO_CACHE') || 0;
100              
101             # see if a 'xxxx_default' or 'default' configuration option is specified
102             # or look for the first XXXX_DEFAULT or DEFAULT package variable.
103             $default = $config->{ $idefault }
104             || $config->{ default }
105 25   33     395 || $class->any_var_in( uc $idefault, uc DEFAULT );
106 25 50       84 if ($default) {
107 0         0 $self->debug("Setting default to $default") if DEBUG;
108 0         0 $self->{ default } = $self->{ names }->{ default } = $default;
109             }
110              
111             $self->debug(
112             "Initialised $item/$items factory\n",
113             " Path: ", $self->dump_data($self->{ path }), "\n",
114             "Names: ", $self->dump_data($self->{ names })
115 25         40 ) if DEBUG;
116              
117 25         100 return $self;
118             }
119              
120             sub path {
121 46     46 1 119 my $self = shift->prototype;
122             return @_
123             ? ($self->{ path } = ref $_[0] eq ARRAY ? shift : [ @_ ])
124 46 0       138 : $self->{ path };
    50          
125             }
126              
127             sub default {
128 1     1 1 3 my $self = shift->prototype;
129             return @_
130             ? ($self->{ default } = $self->{ names }->{ default } = shift)
131 1 50       7 : $self->{ default };
132             }
133              
134             sub items {
135 0     0 1 0 my $self = shift->prototype;
136 0         0 my $items = $self->{ $self->{ items } };
137 0 0       0 if (@_) {
138 0 0       0 my $args = ref $_[0] eq HASH ? shift : { @_ };
139 0         0 @$items{ keys %$args } = values %$args;
140             }
141 0         0 return $items;
142             }
143              
144             sub item {
145 85 100   85 1 196 my $self = shift; $self = $self->prototype unless ref $self;
  85         240  
146 85         243 my ($type, @args) = $self->type_args(@_);
147              
148             # In most cases we're expecting $type to be a name (e.g. Table) which we
149             # lookup in the items hash, or tack onto one of the module bases in the
150             # path (e.g. Template::Plugin) to create a full module name which we load
151             # and instantiate (e.g. Template::Plugin::Table). However, the name might
152             # be explicitly mapped to a reference of some kind, or the $type passed
153             # in could already be a reference (e.g. Template::TT2::Filters allow the
154             # first argument to be a code ref or object which implements the required
155             # filtering behaviour). In which case, we bypass any name-based lookup
156             # and skip straight onto the "look what I found!" phase
157              
158 85 100       264 return $self->found($type, $type, \@args)
159             unless textlike $type;
160              
161 83         149 $type = $type . ''; # auto-stringify any textlike objects
162              
163             # OK, so $type is a string. We'll also create a canonical version of the
164             # name (lower case dotted) to provide a case/syntax insensitve fallback
165             # (e.g. so "foo.bar" can match against "Foo.Bar", "Foo::Bar" and so on)
166              
167 83         199 my $items = $self->{ $self->{ items } };
168 83         204 my $canon = dotid $type;
169              
170 83         109 $self->debug("Looking for '$type' or '$canon' in $self->{ items }") if DEBUG;
171             # $self->debug("types: ", $self->dump_data($self->{ types })) if DEBUG;
172              
173             # false but defined entry indicates the item is not found
174             return $self->not_found($type, \@args)
175             if exists $items->{ $type }
176 83 50 66     345 && not $items->{ $type };
177              
178             my $item = $items->{ $type }
179 83   100     433 || $items->{ $canon }
180             # TODO: this needs to be defined-or, like //
181             # Plugins can return an empty string to indicate that they
182             # do nothing.
183             # HMMM.... or does it?
184             || $self->find($type, \@args)
185             # || $self->default($type, \@args)
186             || return $self->not_found($type, \@args);
187              
188             $items->{ $type } = $item
189 81 50       271 unless $self->{ no_cache };
190              
191 81         276 return $self->found($type, $item, \@args);
192             }
193              
194             sub type_args {
195             # Simple method to grok $type and @args from argument list. The only
196             # processing it does is to set $type to 'default' if it is undefined or
197             # false. Subclasses can re-define this to insert their own type mapping or
198             # argument munging, e.g. to inject values into the configuration params
199             # for an object
200 65     65 1 89 shift;
201 65   100     153 my $type = shift || DEFAULT;
202 65         87 my @args;
203              
204 65 50       122 if (ref $type eq HASH) {
205 0         0 @args = ($type, @_);
206 0   0     0 $type = $type->{ type } || DEFAULT;
207             }
208             else {
209 65         131 @args = @_;
210             }
211              
212 65         145 return ($type, @args);
213             }
214              
215             sub find {
216 46     46 1 80 my $self = shift;
217 46         74 my $type = shift;
218 46         145 my $bases = $self->path;
219 46         65 my $module;
220              
221             # run the type through the type map to handle any unusual capitalisation,
222             # spelling, aliases, etc.
223 46   66     175 $type = $self->{ names }->{ $type } || $type;
224              
225 46         86 foreach my $base (@$bases) {
226 54 100       157 return $module
227             if $module = $self->load( $self->module_names($base, $type) );
228             }
229              
230 2         36 return undef;
231             }
232              
233             sub load {
234 54     54 1 89 my $self = shift;
235 54         92 my $loaded = $self->{ loaded };
236              
237 54         99 foreach my $module (@_) {
238             # see if we've previously loaded a module with this name (true
239             # value) or failed to load a module (defined but false value)
240              
241 64 100       217 if ($loaded->{ $module }) {
    50          
242 12         15 $self->debug("$module has been previously loaded") if DEBUG;
243 12         59 return $module;
244             }
245             elsif (defined $loaded->{ $module }) {
246 0         0 next;
247             }
248              
249 24     24   186 no strict REFS;
  24         62  
  24         27140  
250 52         73 $self->debug("attempting to load $module") if DEBUG;
251              
252             # Some filesystems are case-insensitive (like Apple's HFS), so an
253             # attempt to load Badger::Example::foo may succeed, when the correct
254             # package name is actually Badger::Example::Foo. We double-check
255             # by looking for $VERSION or @ISA. This is a bit dodgy because we might be
256             # loading something that has no ISA. Need to cross-check with
257             # what's going on in Badger::Class _autoload()
258              
259 52         63 my $loadname;
260 52 100       139 if ( ($loadname = class($module)->maybe_load) ) {
261 32         48 $self->debug("loaded $module") if DEBUG;
262 32         77 $loaded->{ $module } = $loadname;
263 32         250 return $module
264             }
265              
266 20         41 $self->debug("failed to load $module") if DEBUG;
267             }
268              
269 10         32 return undef;
270             }
271              
272              
273             sub found {
274 83     83 1 1503 my ($self, $type, $item, $args) = @_;
275              
276 83 100       229 if (ref $item) {
277             # if it's a reference we found then forward it onto the appropriate
278             # method, e.g found_array(), found_hash(), found_code(). Fall back
279             # on found_ref()
280 29 100       135 my $iref = blessed($item)
281             ? OBJECT
282             : lc ref $item;
283              
284 29         49 $self->debug(
285             "Looking for handler methods: ",
286             FOUND,'_'.$iref, "() or ",
287             FOUND_REF, "()"
288             ) if DEBUG;
289              
290             my $method
291             = $self->can(FOUND . '_' . $iref)
292             || $self->can(FOUND_REF)
293 29   50     104 || return $self->error_msg( bad_ref => $self->{ item }, $type, $iref );
294              
295 29         95 $item = $method->($self, $type, $item, $args);
296             }
297             else {
298             # otherwise it's the name of a module
299 54         222 $item = $self->found_module($type, $item, $args);
300             }
301              
302             # NOTE: an item can be defined but false, e.g. a Template::Plugin which
303             # return '' from its new() method to indicate it does nothing objecty
304 83 50       194 return unless defined $item;
305              
306 83         97 $self->debug("Found result: $type => $item") if DEBUG;
307              
308             # TODO: what about caching result? Do we always leave that to subclasses?
309 83         219 return $self->result($type, $item, $args);
310             }
311              
312             sub found_module {
313             # This method is called when a module name is found, either by being
314             # predefined in the factory entry table, or loaded on demand from disk.
315             # It ensures the module is loaded and and instantiates an object from the
316             # class name
317 54     54 1 118 my ($self, $type, $module, $args) = @_;
318 54         63 $self->debug("Found module: $type => $module") if DEBUG;
319 54   66     235 $self->{ loaded }->{ $module } ||= class($module)->load;
320 54         188 return $self->construct($type, $module, $args);
321             }
322              
323             sub found_array {
324             # This method is called when an ARRAY reference is found. We assume that
325             # the first item is the module name (which needs to be loaded) and the
326             # second item is the class name (which needs to be instantiated).
327 4     4 1 13 my ($self, $type, $item, $args) = @_;
328 4         11 my ($module, $class) = @$item;
329 4   66     34 $self->{ loaded }->{ $module } ||= class($module)->load;
330 4         30 return $self->construct($type, $class, $args);
331             }
332              
333             sub not_found {
334 2     2 1 7 my ($self, $type, @args) = @_;
335              
336             return $type eq DEFAULT
337             ? $self->error_msg( no_default => $self->{ item } )
338 2 100       21 : $self->error_msg( not_found => $self->{ item }, $type );
339             }
340              
341             sub construct {
342 59     59 1 140 my ($self, $type, $class, $args) = @_;
343 59         71 $self->debug("constructing class: $type => $class") if DEBUG;
344 59         276 return $class->new(@$args);
345             }
346              
347             sub module_names {
348 54     54 1 78 my $self = shift;
349             my @bits =
350 119         263 map { camel_case($_) }
351 54         99 map { split /[\.]+/ } @_;
  108         305  
352              
353             return (
354 54         117 join( PKG, map { ucfirst $_ } @bits ),
  119         407  
355             join( PKG, @bits )
356             );
357             }
358              
359              
360             sub can {
361 35     35 1 78 my ($self, $name) = @_;
362              
363             # upgrade class methods to calls on prototype
364 35 50       79 $self = $self->prototype unless ref $self;
365              
366             # NOTE: this method can get called before we've called init_factory()
367             # to define the item/items members, so we tread carefully.
368 35 50 33     257 if ($self->{ item } && $self->{ item } eq $name) {
    50 33        
369 0         0 return $self->SUPER::can('item');
370             }
371             elsif ($self->{ items } && $self->{ items } eq $name) {
372 0         0 return $self->SUPER::can('items');
373             }
374             else {
375 35         285 return $self->SUPER::can($name);
376             }
377             }
378              
379             sub result {
380 33     33 1 182 $_[2];
381             }
382              
383             sub AUTOLOAD {
384 12     12   108 my ($self, @args) = @_;
385 12         101 my ($name) = ($AUTOLOAD =~ /([^:]+)$/ );
386 12 100       346 return if $name eq 'DESTROY';
387              
388 7         14 $self->debug("AUTOLOAD $name") if DEBUG;
389              
390 7         17 local $RUNAWAY = $RUNAWAY;
391 7 50       21 $self->error("AUTOLOAD went runaway on $name")
392             if ++$RUNAWAY > 10;
393              
394             # upgrade class methods to calls on prototype
395 7 100       74 $self = $self->prototype unless ref $self;
396              
397 7         13 $self->debug("factory item: $self->{ item }") if DEBUG;
398              
399 7 100       57 if ($name eq $self->{ item }) {
    50          
    50          
400 6         27 $self->class->method( $name => $self->can('item') );
401             }
402             elsif ($name eq $self->{ items }) {
403 0         0 $self->class->method( $name => $self->can('items') )
404             }
405             elsif (my $item = $self->try( item => $name, @args )) {
406 0         0 return $item;
407             }
408             else {
409 1         5 my ($pkg, $file, $line) = caller;
410 1   33     10 my $class = ref $self || $self;
411 1         8 die $self->message( bad_method => $name, $class, $file, $line ), "\n";
412             }
413              
414             # should be installed now
415 6         24 $self->$name(@args);
416             }
417              
418              
419             1;
420              
421             __END__