File Coverage

blib/lib/Test/C2FIT/TypeAdapter.pm
Criterion Covered Total %
statement 103 125 82.4
branch 23 44 52.2
condition 8 18 44.4
subroutine 26 31 83.8
pod 2 15 13.3
total 162 233 69.5


line stmt bran cond sub pod time code
1             # $Id: TypeAdapter.pm,v 1.7 2008/01/24 14:28:26 tonyb Exp $
2             #
3             # Copyright (c) 2002-2005 Cunningham & Cunningham, Inc.
4             # Released under the terms of the GNU General Public License version 2 or later.
5             #
6             # Perl translation by Dave W. Smith
7             # Modified by Tony Byrne
8              
9             package Test::C2FIT::TypeAdapter;
10 3     3   18 use Test::C2FIT::Exception;
  3         6  
  3         48  
11 3     3   3240 use Test::C2FIT::ScientificDouble;
  3         8  
  3         95  
12 3     3   19 use Error qw( :try );
  3         4  
  3         18  
13              
14 3     3   467 use strict;
  3         7  
  3         4399  
15              
16             # Class methods
17              
18             sub onMethod {
19 1     1 0 3 my ( $pkg, $fixture, $name ) = @_;
20              
21 1         4 my $a =
22             $pkg->onType( $fixture, $pkg->_guessMethodResultType( $fixture, $name ) );
23 1         3 $a->{'method'} = $name;
24 1         4 return $a;
25             }
26              
27             sub onField {
28 5     5 0 12 my ( $pkg, $fixture, $name ) = @_;
29              
30 5         17 my $a = $pkg->onType( $fixture, $pkg->_guessFieldType( $fixture, $name ) );
31 5         11 $a->{'field'} = $name;
32 5         21 return $a;
33             }
34              
35             #
36             # Distinction between onMethod and onSetter:
37             # - onMethod - the method result type is assigned a TypeAdapter ("name" is the method name)
38             # - onSetter - the method first (and only) parameter is assigned a TypeAdapter ("name" is the method name)
39             #
40             sub onSetter {
41 0     0 0 0 my ( $pkg, $fixture, $name ) = @_;
42              
43 0         0 my $a =
44             $pkg->onType( $fixture, $pkg->_guessMethodParamType( $fixture, $name ) );
45 0         0 $a->{'method'} = $name;
46 0         0 return $a;
47             }
48              
49             #
50             # returns a fully qualified package name of appropriate Adapter
51             #
52             sub _guessFieldType {
53 5     5   8 my ( $pkg, $fixture, $name ) = @_;
54              
55 5         25 my $typeName = $fixture->suggestFieldType($name);
56              
57 5 50       16 if ( !defined($typeName) ) {
58              
59             # n.b., Field might not exist when we're asked to build a TypeAdapter
60             # for accessing them. This can be addressed by adopting the convention
61             # of populating the object at creation time, rather than lazily, at
62             # least for those fields we're interested in.
63              
64 5         16 my $object = $fixture->{$name};
65 5 50       15 if ( defined($object) ) {
66              
67             #DEBUG print "_guessType: ", ref($object), "\n" if ref($object);
68 0 0       0 $typeName = "Test::C2FIT::GenericArrayAdapter"
69             if ref($object) eq "ARRAY";
70             }
71             }
72 5 50       13 $typeName = "Test::C2FIT::GenericAdapter" unless defined($typeName);
73 5         27 return $typeName;
74             }
75              
76             sub _guessMethodResultType {
77 1     1   2 my ( $pkg, $fixture, $name ) = @_;
78              
79 1         12 my $typeName = $fixture->suggestMethodResultType($name);
80 1 50       5 $typeName = "Test::C2FIT::GenericAdapter" unless defined($typeName);
81 1         5 return $typeName;
82             }
83              
84             sub _guessMethodParamType {
85 0     0   0 my ( $pkg, $fixture, $name ) = @_;
86              
87 0         0 my $typeName = $fixture->suggestMethodParamType($name);
88 0 0       0 $typeName = "Test::C2FIT::GenericAdapter" unless defined($typeName);
89 0         0 return $typeName;
90             }
91              
92             sub onType {
93 6     6 0 9 my ( $pkg, $fixture, $typeAdapterName ) = @_;
94 6         17 my $a = $pkg->_createInstance($typeAdapterName);
95 6         20 $a->init( $fixture, $typeAdapterName );
96 6         9 $a->{'target'} = $fixture;
97 6         13 return $a;
98             }
99              
100             sub _createInstance {
101 6     6   8 my ( $self, $packageName ) = @_;
102 6         6 my $instance;
103              
104 6 50       20 throw Test::C2FIT::Exception("Missing Parameter in _createInstance!")
105             unless defined($packageName);
106              
107             try {
108 6     6   151 $instance = $packageName->new();
109             }
110 6     2   37 otherwise {};
  2         311  
111 6 100       84 if ( !ref($instance) ) {
112             try {
113 2     2   202 eval "use $packageName;";
  2     2   1273  
  2         5  
  2         33  
114 2         15 $instance = $packageName->new();
115             }
116             otherwise {
117 0     0   0 my $e = shift;
118 0         0 throw Test::C2FIT::Exception("Can't load $packageName: $e");
119 2         17 };
120             }
121              
122             throw Test::C2FIT::Exception(
123 6 50       39 "$packageName - instantiation error") # if new does not return a ref...
124             unless ref($instance);
125              
126 6 50       33 throw Test::C2FIT::Exception("$packageName - is not a TypeAdapter!")
127             unless $instance->isa('Test::C2FIT::TypeAdapter');
128              
129 6         14 return $instance;
130             }
131              
132             # Instance creation
133              
134             sub new {
135 6     6 0 8 my $pkg = shift;
136 6         37 bless { instance => undef, type => undef, @_ }, $pkg;
137             }
138              
139             # Instance methods
140              
141             sub init {
142 6     6 0 9 my $self = shift;
143 6         10 my ( $fixture, $type ) = @_;
144 6         19 $self->{'fixture'} = $fixture;
145 6         13 $self->{'type'} = $type;
146             }
147              
148             sub target {
149 6     6 0 8 my $self = shift;
150 6         9 my ($target) = @_;
151 6         16 $self->{'target'} = $target;
152             }
153              
154             sub field {
155 22     22 0 38 my $self = shift;
156 22         120 return $self->{'field'};
157             }
158              
159             sub method {
160 4     4 0 6 my $self = shift;
161 4         21 return $self->{'method'};
162             }
163              
164             sub get {
165 8     8 0 19 my $self = shift;
166 8 100       22 return $self->{'target'}->{ $self->field() } if $self->field();
167 2 50       11 return $self->invoke() if $self->method();
168 0         0 return undef;
169             }
170              
171             sub set {
172 6     6 0 7 my $self = shift;
173 6         8 my ($value) = @_;
174 6         11 my $field = $self->{'field'};
175 6 50       11 throw Test::C2FIT::Exception("can't set without a field\n") unless $field;
176 6         22 $self->{'target'}->{$field} = $value;
177             }
178              
179             sub invoke {
180 2     2 0 3 my $self = shift;
181 2         5 my $method = $self->{'method'};
182 2 50       6 throw Test::C2FIT::Exception("can't invoke without method\n")
183             unless $method;
184 2         8 $self->{'target'}->$method();
185             }
186              
187             sub parse {
188 0     0 1 0 my $self = shift;
189 0         0 my ($s) = @_;
190              
191             # is this right, or do we assume that all subclasses will override?
192 0         0 return $self->{'fixture'}->parse($s);
193             }
194              
195             sub equals {
196 6     6 0 10 my $self = shift;
197 6         9 my ( $a, $b ) = @_;
198 6 50       15 if ( !defined($a) ) {
199 0         0 return !defined($b);
200             }
201              
202             #
203             # if the instance has an equals method, use it
204             #
205             # ( $] > 5.008 )
206             # ? UNIVERSAL->can( $a, "equals" )
207             # : UNIVERSAL::can( $a, "equals" );
208              
209 6         46 my $can = UNIVERSAL::can( $a, "equals" );
210 6 50       24 return $a->equals($b) if ($can);
211              
212             # We need to be ugly to handle booleans
213 6 50 33     18 return 1 if $a eq "true" and $b == 1;
214 6 50 33     23 return 1 if $a eq "false" and $b == 0;
215              
216             # We need to be ugly here to handle numbers
217 6 100 66     22 if ( $self->_isnumber($a) and $self->_isnumber($b) ) {
218 2         21 my $scA = Test::C2FIT::ScientificDouble->new($a);
219 2         8 my $scB = Test::C2FIT::ScientificDouble->new($b);
220 2         9 return $scA->equals($scB);
221             }
222              
223 4         31 return $a eq $b;
224             }
225              
226             sub _isnumber {
227 8     8   14 my ($self, $test) = @_;
228            
229             # Handle fractions.
230 8 50       23 if ($test =~ /\//)
231             {
232 0         0 my ($a, $b) = split /\//, $test;
233 0   0     0 return $self->_isnumber($a) && $self->_isnumber($b);
234             }
235            
236 8         30 defined scalar $self->_getnum($test);
237             }
238              
239             sub _getnum {
240 3     3   2849 use POSIX qw(strtod);
  3         24948  
  3         26  
241 8     8   15 my ($self, $str) = @_;
242 8         15 $str =~ s/^\s+//;
243 8         16 $str =~ s/\s+$//;
244 8         15 $! = 0;
245 8         73 my($num, $unparsed) = strtod($str);
246 8 100 66     94 if (($str eq '') || ($unparsed != 0) || $!) {
      66        
247 4         20 return;
248             } else {
249 4         21 return $num;
250             }
251             }
252              
253             sub toString {
254 0     0 1 0 my $self = shift;
255 0         0 my ($o) = @_;
256 0 0       0 $o = "null" unless defined $o;
257 0         0 return $o;
258             }
259              
260             1;
261              
262             =head1 NAME
263              
264             Test::C2FIT::TypeAdapter - Base class of all TypeAdapters.
265              
266              
267             =head1 SYNOPSIS
268              
269             You typically subclass TypeAdapter. Implement at least parse(), eventually equals() and toString().
270              
271              
272             =head1 DESCRIPTION
273              
274              
275             When your data is not stored as string, then you'll propably need an TypeAdapter.
276             E.g.: duration, which is displayed (and entered) in the form "MMM:SS" but stored as number of seconds.
277              
278             =head1 METHODS
279              
280             =over 4
281              
282             =item B
283              
284             Returns the internal representation of $string. Either this is an object instance, but it can be also a scalar
285             value.
286              
287             =item B
288              
289             Returns the stringified representation of the internal value.
290              
291             =back
292              
293             =head1 SEE ALSO
294              
295             Extensive and up-to-date documentation on FIT can be found at:
296             http://fit.c2.com/
297              
298              
299             =cut
300              
301             __END__