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