File Coverage

blib/lib/Devel/StrictObjectHash.pm
Criterion Covered Total %
statement 86 115 74.7
branch 44 88 50.0
condition 4 11 36.3
subroutine 14 23 60.8
pod 4 5 80.0
total 152 242 62.8


line stmt bran cond sub pod time code
1              
2             package Devel::StrictObjectHash;
3              
4 6     6   142708 use strict;
  6         12  
  6         188  
5 6     6   26 use warnings;
  6         10  
  6         1896  
6              
7             our $VERSION = '0.01';
8              
9             ## ----------------------------------------------------------------------------
10             ## Debugging functions
11             ## ----------------------------------------------------------------------------
12              
13             # make a sub
14 249     249 0 650 sub DEBUG { 0 }
15              
16             {
17             # this should not be accessable
18             # to anyone but the debug function
19             my $debug_line_number = 1;
20             # debuggin'
21             sub debug {
22             # otherwise debug
23 0     0 1 0 my $formatted_debug_line_number = sprintf("%03d", $debug_line_number);
24 0         0 print STDERR "debug=($formatted_debug_line_number) ", @_, "\n";
25 0         0 $debug_line_number++;
26             }
27             }
28              
29             ## ----------------------------------------------------------------------------
30             ## package variables
31             ## ----------------------------------------------------------------------------
32              
33             my $KEY_CREATION_ACCESS_REGEX = qr/^new$/;
34             my $PRIVATE_FIELD_ACCESS_REGEX = qr/^new$/;
35              
36             my $PUBLIC_FIELD_IDENTIFIER = undef;
37             my $PROTECTED_FIELD_IDENTIFIER = qr/^[a-zA-Z][a-zA-Z0-9_]+/;
38             my $PRIVATE_FIELD_IDENTIFIER = qr/^_/;
39              
40             my $INC_FILTER_REG_EX = undef;
41             my $INC_filter = sub {
42             my ($code, $file_name) = @_;
43             # we dont handle anything other than
44             # .pm files for now
45             return undef unless $file_name =~ /\.pm$/;
46             # get the package name
47             my $package_name = $file_name;
48             $package_name =~ s/\.pm$//;
49             $package_name =~ s/\//\:\:/g;
50             # now create the package now, but
51             # also create a bless routine in it
52             if ($package_name =~ /$INC_FILTER_REG_EX/ || $file_name =~ /$INC_FILTER_REG_EX/) {
53             debug("+ creating bless routine in ${file_name}") if DEBUG;
54 6     6   32 no strict 'refs';
  6         23  
  6         1789  
55             *{"${package_name}::bless"} = \&Devel::StrictObjectHash::strict_bless;
56             }
57             # tell the world
58             debug("^ loading $file_name package through INC filter") if DEBUG;
59             # and let it be loaded normally
60             return undef;
61             };
62              
63             my $ERROR_TYPE = 'die';
64             my %ERROR_HANDLERS = (
65             "warn" => sub { warn "* ", __PACKAGE__, " [", (scalar localtime), "] ", @_ },
66             "die" => sub { die @_ }
67             );
68            
69             sub handleError {
70 12     12 1 36 $ERROR_HANDLERS{$ERROR_TYPE}->(@_)
71             }
72              
73             ## ----------------------------------------------------------------------------
74             ## import interface
75             ## ----------------------------------------------------------------------------
76              
77             sub import {
78 6     6   65 shift;
79 6 50       37 ((scalar(@_) % 2) == 0) || die "uneven parameter assignment for Devel::StrictObjectHash";
80 6         26 my %params = @_;
81             # change bless as nessecary
82 6 50       23 if (exists $params{"strict_bless"}) {
83 6         12 my $how_to_bless = $params{"strict_bless"};
84 6 100       30 if (ref($how_to_bless) eq "ARRAY") {
    50          
    0          
85 4         8 my @packages = @{$how_to_bless};
  4         11  
86 6     6   52 no strict 'refs';
  6         10  
  6         873  
87 4         19 *{"${_}::bless"} = \&Devel::StrictObjectHash::strict_bless foreach @packages;
  5         44  
88             }
89             elsif (ref($how_to_bless) eq "Regexp") {
90 2         3 $INC_FILTER_REG_EX = $how_to_bless;
91 2         4 unshift @INC => $INC_filter;
92             }
93             elsif ($how_to_bless eq "global") {
94 0         0 *CORE::GLOBAL::bless = \&Devel::StrictObjectHash::strict_bless;
95             }
96             else {
97 0         0 die "unrecognized parameter ($how_to_bless) for 'strict_bless'";
98             }
99             }
100             # turn on debugging
101 6 50       32 if (exists $params{"debug"}) {
102 6     6   28 no warnings 'redefine';
  6         16  
  6         1865  
103 0 0   0   0 *Devel::StrictObjectHash::DEBUG = sub { 1 } if $params{"debug"};
  0         0  
104             }
105             # change $KEY_CREATION_ACCESS_REGEX
106 6 100       22 if (exists $params{"allow_autovivification_in"}) {
107             # if is this param is a reg-ex, we need to add 'new' to it
108             # so we can handle strings and qr// stuff the same way
109 2         49 $KEY_CREATION_ACCESS_REGEX = qr/$params{"allow_autovivification_in"}|new/;
110             }
111             # change $PUBLIC, $PROTECTED, $PRIVATE
112 6 100       41 if (exists $params{"field_access_identifiers"}) {
113 1         2 my %field_identifiers = %{$params{"field_access_identifiers"}};
  1         6  
114 1 50       5 $PUBLIC_FIELD_IDENTIFIER = $field_identifiers{"public"} if exists $field_identifiers{"public"};
115 1 50       4 $PROTECTED_FIELD_IDENTIFIER = $field_identifiers{"protected"} if exists $field_identifiers{"protected"};
116 1 50       6 $PRIVATE_FIELD_IDENTIFIER = $field_identifiers{"private"} if exists $field_identifiers{"private"};
117             }
118             # change $ERROR_TYPE
119 6 100       64 if (exists $params{"error_handling"}) {
120 1 50       5 (exists $ERROR_HANDLERS{$params{"error_handling"}})
121             || die "error handling style (" . $params{"error_handling"}. ") not available";
122 1         11 $ERROR_TYPE = $params{"error_handling"};
123             }
124             }
125              
126             ## ----------------------------------------------------------------------------
127             ## bless function
128             ## ----------------------------------------------------------------------------
129              
130             # you can use this to replace bless
131             # it is best done by importing this
132             # method into the module you wish to
133             # use Devel::StrictObjectHash in.
134             # NOTE:
135             # this explicity disallows it to tie
136             # itself so it will not create any
137             # deep-recusion.
138             sub strict_bless {
139 5     5 1 2704 my ($hash, $class) = @_;
140 5 50       26 debug("* tying hash ($hash) with Devel::StictObjectHash for $class") if DEBUG;
141             # do not allow it to tie itself
142 5 50       27 tie(%{$_[0]}, "Devel::StrictObjectHash", $class, %{$hash}) unless ($class eq "Devel::StrictObjectHash");
  5         32  
  5         46  
143             # since this may be used to override the
144             # actual bless function, then we should
145             # be explict here and use CORE::bless
146 5         23 return CORE::bless($_[0], $class);
147             }
148              
149             ## ----------------------------------------------------------------------------
150             ## class methods
151             ## ----------------------------------------------------------------------------
152              
153 6     6   6235 use Data::Dumper ();
  6         63873  
  6         8078  
154              
155             sub Dump {
156 0     0 1 0 my ($object) = @_;
157 0   0     0 my $tied_hash = tied(%{$object}) || die "not a Devel::StrictObjectHash object";
158 0 0       0 if ($tied_hash->isa("Devel::StrictObjectHash")) {
159 0         0 return "dumping: $tied_hash\n" . Data::Dumper::Dumper($tied_hash);
160             }
161             }
162              
163              
164             ## ----------------------------------------------------------------------------
165             ## tie functions
166             ## ----------------------------------------------------------------------------
167              
168             sub TIEHASH {
169 5     5   27 my ($class, $blessed_class, %_hash) = @_;
170 5 50       13 debug("class=($class) blessed_class($blessed_class) hash-keys=(" . (join ", " => keys %_hash) . ")") if DEBUG;
171             # we need to get the name of the class that
172             # actually called us (it should be at least
173             # one away since you should be using strict_bless)
174             # this will tell is what class is doing the
175             # actual initialization
176 5         37 my ($actual_calling_class) = caller(1);
177 11         49 my $hash = {
178             # store the class this has is going to
179             # be blessed into
180             blessed_class => $blessed_class,
181             # store the initial hash fields
182             fields => \%_hash,
183             # this stores a reference to what class
184             # the fields were actually initialized int
185             # this is important for private fields
186             # and being able to check if they are being
187             # stepped upon or not
188 5         22 fields_init_in => { map { $_ => $actual_calling_class } keys %_hash }
189             };
190 5         27 bless($hash, $class);
191 5         1434 return $hash;
192             }
193              
194             ## HASH tie routines
195              
196             sub STORE {
197 27     27   23310 my ($self, $key, $value) = @_;
198 27 50       304 debug("^ calling STORE on key=($key) for object=($self) ") if DEBUG;
199             # first we need to check to see if
200             # the user has the right to access
201             # this field
202 27         70 $self->_check_access($key);
203             # and log this activity
204 17 0       38 debug("> storing value=(" .
    50          
205             # this avoids unnessecary warnings
206             ((defined($value)) ? $value : "undef") .
207             ") at key=($key) in subroutine=(" .
208             # this is the name of the subroutine
209             # that called this function
210             (caller(1))[3] . ")") if DEBUG;
211 17         84 $self->{fields}->{$key} = $value;
212             }
213              
214             sub FETCH {
215 28     28   21962 my ($self, $key) = @_;
216 28 50       56 debug("^ calling FETCH on key=($key) for object=($self) ") if DEBUG;
217             # first we need to check to see if
218             # the user has the right to access
219             # this field
220 28         75 $self->_check_access($key);
221             # and log the activity
222 26 0       64 debug("< fetching value=(" .
    50          
223             # this avoids unnessecary warnings
224             # NOTE:
225             # we say defined here, not exists because
226             # we know the field exists, otherwise the
227             # _check_access method would have failed
228             # we just need to see if the value is not
229             # undef, to avoid the warnings.
230             ((defined($self->{fields}->{$key})) ?
231             $self->{fields}->{$key}
232             :
233             "undef") .
234             ") at key=($key) in subroutine=(" .
235             # this is the name of the subroutine
236             # that called this function
237             (caller(1))[3] . ")") if DEBUG;
238 26         126 return $self->{fields}->{$key};
239             }
240              
241             # NOTE:
242             # the following 2 methods are rarely used, but
243             # in the interest in providing a complete interface
244             # to the object, we will implement.
245              
246             # checking existence is rare, since an
247             # object should know its fields (its protected
248             # ones anyway). Good encapsulation dictates that
249             # the private fields should be hidden though, so
250             # we use _check_access.
251             # NOTE:
252             # it might make sense to catch the exception
253             # thrown by _check_access here and return something
254             # but then again, I would not want to give the
255             # false impression that the field is NOT there.
256             sub EXISTS {
257 0     0   0 my ($self, $key) = @_;
258 0 0       0 debug("^ calling EXISTS on key=($key) for object=($self) ") if DEBUG;
259             # first we need to check to see if
260             # the user has the right to access
261             # this field
262 0         0 $self->_check_access($key);
263 0         0 return exists $self->{fields}->{$key};
264             }
265              
266             # deletion too is something done rarely, usually
267             # only in the destructor method (DESTROY), so
268             # we will enforce access control here as usual
269             sub DELETE {
270 0     0   0 my ($self, $key) = @_;
271 0 0       0 debug("^ calling DELETE on key=($key) for object=($self) ") if DEBUG;
272             # first we need to check to see if
273             # the user has the right to access
274             # this field
275 0         0 $self->_check_access($key);
276 0         0 delete $self->{fields}->{$key};
277             }
278              
279              
280             # NOTE:
281             # the following 2 methods (FIRSTKEY and NEXTKEY) are
282             # for supporting the keys, values and each functions
283             # on hashes. These are not common things done with
284             # the hashes used to form the basis of objects.
285              
286             sub FIRSTKEY {
287 0     0   0 my ($calling_package) = caller(0);
288 0         0 handleError "Illegal Operation : calling FIRSTKEY not supported from $calling_package";
289             }
290              
291             sub NEXTKEY {
292 0     0   0 my ($calling_package) = caller(0);
293 0         0 handleError "Illegal Operation : calling NEXTKEY not supported from $calling_package";
294             }
295              
296             # NOTE:
297             # the following 2 methods are not allowed at all.
298             # a user should never clear all the fields of an
299             # object, that just doesnt make sense. And untie-ing
300             # of this object would violate the intent of this
301             # module (to provide a drop in bless replacement
302             # for debugging object field access issues)
303              
304             sub CLEAR {
305 0     0   0 handleError "Illegal Operation : Clearing of this hash is strictly forbidden";
306             }
307              
308             sub UNTIE {
309 0     0   0 handleError "Illegal Operation : Un-tie-ing of this hash is strictly forbidden";
310             }
311              
312             ## Private subroutine
313              
314             # to check the access of our hash
315             sub _check_access {
316 55     55   77 my ($self, $key) = @_;
317 55 50       83 debug(" ? checking access for key=($key)") if DEBUG;
318 55         506 my ($calling_package, undef, undef, $hash_action) = caller(1);
319 55 100       209 ($calling_package ne "main") || handleError "Illegal Operation : hashes cannot be accessed directly";
320 45         281 my (undef, undef, undef, $_calling_subroutine) = caller(2);
321 45         215 my ($calling_subroutine) = ($_calling_subroutine =~ /\:\:([a-zA-Z0-9_]+)$/);
322             # we do not handle DESTROY methods, they get funky
323 45 50       119 return if $calling_subroutine =~ /DESTROY/;
324             # check if our key ever exists ...
325 45 100       149 unless (exists $self->{fields}->{$key}) {
326             # if our field does not exist, then we should throw an exception
327             # we want to do this to protect ourselves against mis-spellings
328             # of field names. This means that we are only allowed to create
329             # fields before the hash is blessed. We do however allow one repreise
330             # which is for a field to be created inside of the "new" method.
331             #
332             # here we check to see if they are in a methos allowed by the
333             # $KEY_CREATION_ACCESS_REGEX and if not throw an
334             # IllegalOperation exception...
335 5 50       11 debug(" ? attempting to autovivify key=($key) in method=($calling_subroutine) from package=($calling_package)") if DEBUG;
336 5 50       46 ($calling_subroutine =~ /$KEY_CREATION_ACCESS_REGEX/)
337             || handleError "Illegal Operation : attempt to create non-existant key ($key) in method '$calling_subroutine'";
338             # however, if they are in method allowed by $KEY_CREATION_ACCESS_REGEX
339             # the then we allow the field to be created (this happens in STORE) and
340             # we note which package asked for it to be created.
341 5 50       13 debug(" + autovivified key=($key) in hash in method=($calling_subroutine) from package=($calling_package)") if DEBUG;
342 5         17 $self->{fields_init_in}->{$key} = $calling_package;
343             }
344             # if our key does exist then,
345             # check who is asking for it
346             else {
347             # first lets check the private fields ...
348 40 100 66     308 if ($key =~ /$PRIVATE_FIELD_IDENTIFIER/) {
    100          
    100          
349             # we need to check to see if this is
350             # being called from an $KEY_CREATION_ACCESS_REGEX method and ...
351 17 0 33     74 if ($calling_subroutine =~ /$KEY_CREATION_ACCESS_REGEX/ &&
      33        
352             # if it is being asked to STORE a value
353             $hash_action =~ /\:\:STORE$/ &&
354             # and that the calling package is
355             # not the same package who initialized
356             # the field
357             $calling_package ne $self->{fields_init_in}->{$key}) {
358             # if all these conditions meet then we
359             # have a problem ...
360            
361             # first lets check if the package which
362             # initialized the field is actually a
363             # descendant of the calling package.
364             # Meaning that the child package may
365             # have intialized a private field that
366             # the parent had reserved, but not yet
367             # intialized (this can happen if you
368             # run your _init routines before you
369             # run the parents).
370 0 0       0 if ($self->{fields_init_in}->{$key}->isa($calling_package)) {
    0          
371 0         0 handleError "Illegal Operation : It seems that " .
372             $self->{fields_init_in}->{$key} .
373             " maybe stepping on one of ${calling_package}'s private fields ($key)";
374             }
375             # next we check to see if maybe the
376             # calling package is a descendent of the
377             # package the field was intialized in.
378             # Meaning that the child package is
379             # stepping on a private field from the
380             # parent.
381             elsif ($calling_package->isa($self->{fields_init_in}->{$key})) {
382 0         0 handleError "Illegal Operation : $calling_package is stepping on a private field ($key) that belongs to " . $self->{fields_init_in}->{$key};
383             }
384             # and lastly our fall through case, since
385             # no-one should be doing this anyway.
386             else {
387 0         0 handleError "Illegal Operation : attempting to set a private field ($key) in $calling_subroutine, field was already set by " . $self->{fields_init_in}->{$key};
388             }
389             }
390             # okay now we know that is all set
391             #
392             # For a private field we need to check
393             # if the calling package is the same as
394             # the package the field was intialized in.
395             # If it is, then all is fine.
396             # However, if it is not we need to check
397             # on some things ...
398 17 50       28 debug(">>> calling package=($calling_package) package init in=($self->{fields_init_in}->{$key})") if DEBUG;
399 17 100       58 unless ($calling_package eq $self->{fields_init_in}->{$key}) {
400             # ocasionally the calling package is
401             # actually the derived class (because
402             # its a dynamic method call), in which case
403             # the call may actually still be valid,
404             # so we check the calling subroutine.
405             # That subroutine name will contain the
406             # name of the package from where it originated,
407             # and therefore tell us if the privacy is being
408             # violated or not.
409 2 50       59 ($calling_subroutine =~ /^$self->{fields_init_in}->{$key}\:\:/)
410             || handleError "Illegal Operation : $calling_package ($calling_subroutine) attempted to access private field ($key) for " . $self->{fields_init_in}->{$key};
411             }
412             }
413             # now we check the protected fields ....
414             elsif ($key =~ /$PROTECTED_FIELD_IDENTIFIER/) {
415             # a protected field is one that can only be accessed
416             # if the calling package is a descendent of the
417             # orginal class it was blessed into, or the actual
418             # class itself
419 18 50       108 ($self->{blessed_class}->isa($calling_package))
420             || handleError "Illegal Operation : $calling_package attempted to access protected field ($key) for " . $self->{blessed_class};
421             }
422             elsif (defined($PUBLIC_FIELD_IDENTIFIER) && $key =~ /$PUBLIC_FIELD_IDENTIFIER/) {
423             # if the field is public, then we dont
424             # need to do anything, access is granted.
425             # we do though, check to see if the public
426             # reg-ex is even defined, as it may not be
427             ;
428             }
429             }
430             # if we return normally from this
431             # subtroutine, meaning no exceptions
432             # were thrown, then all is well in our
433             # hash accessing world.
434 43 50       75 debug(" + access granted for key=($key)") if DEBUG;
435             }
436              
437              
438             1;
439              
440             __END__