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   610 use Badger::Debug ':dump';
  24         43  
  24         149  
27             use Badger::Class
28 24         318 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   174 };
  24         61  
49              
50             our $RUNAWAY = 0;
51             our $AUTOLOAD;
52              
53             *init = \&init_factory;
54              
55              
56             sub init_factory {
57 25     25 0 68 my ($self, $config) = @_;
58 25         83 my $class = $self->class;
59 25         59 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       91 unless ($item = $config->{ item }) {
65 24         209 foreach my $pkg ($class->heritage) {
66 24     24   165 no strict REFS;
  24         45  
  24         898  
67 24     24   131 no warnings ONCE;
  24         48  
  24         25194  
68              
69 24 50       43 if (defined ($item = ${ $pkg.PKG.ITEM })) {
  24         64  
70 24         41 $items = ${ $pkg.PKG.ITEMS };
  24         58  
71 24         52 last;
72             }
73             }
74             }
75 25 50       80 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     242 $items = $config->{ items } || $items || plural($item);
80              
81 25         67 my $ipath = $item.PATH_SUFFIX;
82 25         52 my $inames = $item.NAMES_SUFFIX;
83 25         45 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         123 my @path = @$config{ path => lc $ipath };
89 25         57 my @names = @$config{ names => lc $inames };
90              
91 25         168 $self->{ path } = $class->list_vars(uc $ipath, @path);
92 25         134 $self->{ names } = $class->hash_vars(uc $inames, @names);
93 25         138 $self->{ $items } = $class->hash_vars(uc $items, $config->{ $items });
94 25         86 $self->{ items } = $items;
95 25         62 $self->{ item } = $item;
96 25         48 $self->{ loaded } = { };
97             $self->{ no_cache } = defined $config->{ no_cache } # quick hack
98             ? $config->{ no_cache }
99 25 50 50     208 : $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     305 || $class->any_var_in( uc $idefault, uc DEFAULT );
106 25 50       94 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         47 ) if DEBUG;
116              
117 25         80 return $self;
118             }
119              
120             sub path {
121 46     46 1 107 my $self = shift->prototype;
122             return @_
123             ? ($self->{ path } = ref $_[0] eq ARRAY ? shift : [ @_ ])
124 46 0       130 : $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       6 : $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 190 my $self = shift; $self = $self->prototype unless ref $self;
  85         250  
146 85         242 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       237 return $self->found($type, $type, \@args)
159             unless textlike $type;
160              
161 83         152 $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         192 my $items = $self->{ $self->{ items } };
168 83         194 my $canon = dotid $type;
169              
170 83         115 $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     346 && not $items->{ $type };
177              
178             my $item = $items->{ $type }
179 83   100     375 || $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       253 unless $self->{ no_cache };
190              
191 81         282 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 90 shift;
201 65   100     174 my $type = shift || DEFAULT;
202 65         84 my @args;
203              
204 65 50       134 if (ref $type eq HASH) {
205 0         0 @args = ($type, @_);
206 0   0     0 $type = $type->{ type } || DEFAULT;
207             }
208             else {
209 65         145 @args = @_;
210             }
211              
212 65         159 return ($type, @args);
213             }
214              
215             sub find {
216 46     46 1 76 my $self = shift;
217 46         62 my $type = shift;
218 46         134 my $bases = $self->path;
219 46         61 my $module;
220              
221             # run the type through the type map to handle any unusual capitalisation,
222             # spelling, aliases, etc.
223 46   66     159 $type = $self->{ names }->{ $type } || $type;
224              
225 46         87 foreach my $base (@$bases) {
226 54 100       177 return $module
227             if $module = $self->load( $self->module_names($base, $type) );
228             }
229              
230 2         14 return undef;
231             }
232              
233             sub load {
234 54     54 1 83 my $self = shift;
235 54         91 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       215 if ($loaded->{ $module }) {
    50          
242 12         29 $self->debug("$module has been previously loaded") if DEBUG;
243 12         60 return $module;
244             }
245             elsif (defined $loaded->{ $module }) {
246 0         0 next;
247             }
248              
249 24     24   178 no strict REFS;
  24         43  
  24         25569  
250 52         69 $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         90 my $loadname;
260 52 100       142 if ( ($loadname = class($module)->maybe_load) ) {
261 32         44 $self->debug("loaded $module") if DEBUG;
262 32         68 $loaded->{ $module } = $loadname;
263 32         234 return $module
264             }
265              
266 20         42 $self->debug("failed to load $module") if DEBUG;
267             }
268              
269 10         27 return undef;
270             }
271              
272              
273             sub found {
274 83     83 1 1452 my ($self, $type, $item, $args) = @_;
275              
276 83 100       203 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       109 my $iref = blessed($item)
281             ? OBJECT
282             : lc ref $item;
283              
284 29         39 $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     111 || 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         193 $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       180 return unless defined $item;
305              
306 83         95 $self->debug("Found result: $type => $item") if DEBUG;
307              
308             # TODO: what about caching result? Do we always leave that to subclasses?
309 83         220 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 114 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         174 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 10 my ($self, $type, $item, $args) = @_;
328 4         8 my ($module, $class) = @$item;
329 4   66     34 $self->{ loaded }->{ $module } ||= class($module)->load;
330 4         21 return $self->construct($type, $class, $args);
331             }
332              
333             sub not_found {
334 2     2 1 8 my ($self, $type, @args) = @_;
335              
336             return $type eq DEFAULT
337             ? $self->error_msg( no_default => $self->{ item } )
338 2 100       18 : $self->error_msg( not_found => $self->{ item }, $type );
339             }
340              
341             sub construct {
342 59     59 1 122 my ($self, $type, $class, $args) = @_;
343 59         69 $self->debug("constructing class: $type => $class") if DEBUG;
344 59         280 return $class->new(@$args);
345             }
346              
347             sub module_names {
348 54     54 1 85 my $self = shift;
349             my @bits =
350 119         257 map { camel_case($_) }
351 54         93 map { split /[\.]+/ } @_;
  108         296  
352              
353             return (
354 54         124 join( PKG, map { ucfirst $_ } @bits ),
  119         399  
355             join( PKG, @bits )
356             );
357             }
358              
359              
360             sub can {
361 35     35 1 79 my ($self, $name) = @_;
362              
363             # upgrade class methods to calls on prototype
364 35 50       93 $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     240 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         251 return $self->SUPER::can($name);
376             }
377             }
378              
379             sub result {
380 33     33 1 122 $_[2];
381             }
382              
383             sub AUTOLOAD {
384 12     12   139 my ($self, @args) = @_;
385 12         95 my ($name) = ($AUTOLOAD =~ /([^:]+)$/ );
386 12 100       339 return if $name eq 'DESTROY';
387              
388 7         11 $self->debug("AUTOLOAD $name") if DEBUG;
389              
390 7         14 local $RUNAWAY = $RUNAWAY;
391 7 50       26 $self->error("AUTOLOAD went runaway on $name")
392             if ++$RUNAWAY > 10;
393              
394             # upgrade class methods to calls on prototype
395 7 100       39 $self = $self->prototype unless ref $self;
396              
397 7         9 $self->debug("factory item: $self->{ item }") if DEBUG;
398              
399 7 100       37 if ($name eq $self->{ item }) {
    50          
    50          
400 6         23 $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         4 my ($pkg, $file, $line) = caller;
410 1   33     3 my $class = ref $self || $self;
411 1         5 die $self->message( bad_method => $name, $class, $file, $line ), "\n";
412             }
413              
414             # should be installed now
415 6         31 $self->$name(@args);
416             }
417              
418              
419             1;
420              
421             __END__