File Coverage

blib/lib/Class/STAF/Marshalled.pm
Criterion Covered Total %
statement 218 282 77.3
branch 49 108 45.3
condition 6 24 25.0
subroutine 20 25 80.0
pod 4 6 66.6
total 297 445 66.7


line stmt bran cond sub pod time code
1             package Class::STAF::Marshalled;
2            
3             our $VERSION = 0.02;
4            
5 3     3   3317 use Data::Dumper;
  3         41236  
  3         349  
6 3     3   31 use Exporter;
  3         7  
  3         14932  
7             our @ISA = qw{Exporter};
8            
9             our @EXPORT = qw{
10             Marshall
11             UnMarshall
12             };
13            
14             our @EXPORT_OK = qw{
15             get_staf_fields
16             get_staf_class_name
17             };
18            
19             our %EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]);
20            
21             # Each Class record is a hash ref containing the following fields:
22             # FieldsDefs - a hash store of fields definitions, containing the following data:
23             # key - the name of the key. example: 'serial'
24             # 'display-name' - description. example: 'serial #'
25             # default - optional. a default for this field. example - 5.
26             # short - optional. a short name for the field. example: 'ser#'
27             # FieldsOrder - the same as FieldsDefs, but stored in array. the fields need an
28             # order to be transmitted.
29             # PackageName - The name of the handling package. example: 'STAF::Service::Var::VarInfo'
30             # SlashedName - Same as PackageName, but with '/' instade of '::'.
31             # example: 'STAF/Service/Var/VarInfo'
32             # Final - 0 if no object was ever created from this class definition, 1 otherwise.
33             #
34             # The records are stored by both PackageName and SlashedName
35             our $classes_store = {};
36            
37             sub field {
38 3     3 0 31 my @params = @_;
39 3         5 my $usage =
40             "usage: \n" .
41             "__PACKAGE__->field(name, description [, default=>5] [, short=>\"ser#\"])\n";
42 3         6 my $err_msg1 = "The Field function should have at least two parameters.\n";
43 3         6 my $err_msg2 = "Received undefined parameters.\n";
44 3         2 my $err_msg3 = "Wrong number of parameters.\n";
45            
46 3 50       9 die $err_msg1 . $usage if @params < 3;
47 3         5 my $class = shift @params;
48 3         6 my $name = shift @params;
49 3         5 my $description = shift @params;
50 3 50 33     22 die $err_msg2 . $usage
      33        
51             unless defined $class and defined $name and defined $description;
52 3 50       9 die $err_msg3 . $usage
53             unless @params % 2 == 0;
54            
55 3         4 my $package_store;
56 3 100       9 if (exists $classes_store->{$class}) {
57 2         3 $package_store = $classes_store->{$class};
58 2 50       7 die "It is not possible to modify class after instiating objects\n"
59             if $package_store->{Final};
60             } else {
61 1         3 my $slashedName = $class;
62 1         5 $slashedName =~ s/::/\//g;
63 1         9 $package_store = {
64             FieldsDefs => {},
65             FieldsOrder => [],
66             PackageName => $class,
67             SlashedName => $slashedName,
68             Final => 0,
69             };
70 1         4 $classes_store->{$class} = $package_store;
71 1         2 $classes_store->{$slashedName} = $package_store;
72             }
73            
74 3 50       9 die "Field $name already exists in class $class\n"
75             if exists $package_store->{FieldsDefs}->{$name};
76 3         9 my $field = {
77             key => $name,
78             'display-name' => $description,
79             };
80 3         8 while (@params) {
81 0         0 my $opt_name = shift @params;
82 0         0 my $opt_value = shift @params;
83 0 0 0     0 die "option name not recognized: $opt_name\n" . $usage
84             unless $opt_name eq "default" or $opt_name eq "short";
85 0         0 $field->{$opt_name} = $opt_value;
86             }
87 3         7 $package_store->{FieldsDefs}->{$name} = $field;
88 3         3 push @{$package_store->{FieldsOrder}}, $field;
  3         12  
89             # print "Dump: ", Dumper($package_store), "\n";
90             }
91            
92             sub new {
93 1     1 0 33 my ($class, @params) = @_;
94 1 50       5 die "Class $class not defined\n" unless exists $classes_store->{$class};
95 1 50       3 die "Parameters list is not balanced\n" unless @params % 2 == 0;
96 1         3 my $package_store = $classes_store->{$class};
97 1         3 $package_store->{Final} = 1;
98 1         1 my %self;
99 1         20 tie %self, 'Class::STAF::Marshalled::_Tied', $package_store;
100 1         3 while (@params) {
101 3         4 my $opt_name = shift @params;
102 3         4 my $opt_value = shift @params;
103 3         13 $self{$opt_name} = $opt_value;
104             }
105 1         2 my $self_ref = \%self;
106 1         4 return bless $self_ref, $class;
107             }
108            
109             sub _internalMarshallSimpleScalar {
110 30     30   37 my ($obj_ref, $defs_store) = @_;
111 30         115 return "\@SDT/\$S:" . length($obj_ref) . ":" . $obj_ref;
112             }
113            
114             sub _internalMarshall {
115 21     21   31 my ($obj_ref, $defs_store) = @_;
116            
117 21 50       41 if (!defined $obj_ref) {
118 0         0 return "\@SDT/\$0:";
119             }
120 21 100       50 if (!ref($obj_ref)) {
121             # it is a simple scalar. marshall it.
122 18         33 return _internalMarshallSimpleScalar($obj_ref, $defs_store);
123             }
124 3 50       27 if (UNIVERSAL::isa($obj_ref, "SCALAR")) {
125             # a reference to a scalar. recurse on it.
126 0         0 my $m = _internalMarshall($$obj_ref, $defs_store);
127 0         0 return "\@SDT/\$S:" . length($m) . ":" . $m;
128             }
129 3 100       17 if (UNIVERSAL::isa($obj_ref, "ARRAY")) {
130             # array reference. no problem.
131 1         2 my @list = map { _internalMarshall($_, $defs_store) } @$obj_ref;
  10         23  
132 1         4 my $m = join('', @list);
133 1         7 return "\@SDT/[" . scalar(@list) . ":" . length($m) . ":" . $m;
134             }
135 2 50       42 die "Unrecognized Data type!\n" unless UNIVERSAL::isa($obj_ref, "HASH");
136 2         10 my $tied_obj = tied(%$obj_ref);
137 2 50 33     20 if (!$tied_obj or !UNIVERSAL::isa($tied_obj, 'Class::STAF::Marshalled::_Tied')) {
138             # this is a simple hash. nothing to see, move along.
139             # @SDT/{:::
140 0         0 my @list;
141 0         0 while (my ($key, $val) = each %$obj_ref) {
142 0         0 my $key_len = length($key);
143 0         0 push @list, $key_len . ":" . $key . _internalMarshall($val, $defs_store);
144             }
145 0         0 my $m = join(":", @list);
146 0         0 return "\@SDT/{:" . length($m) . ":" . $m;
147             }
148             # A Map Class
149 2         7 my $class_name = $tied_obj->[1]->{SlashedName};
150 2         5 $defs_store->{$class_name} = $tied_obj->[1];
151             #@SDT/%::::
152             #
153             # ...
154             #
155 2         5 my @list = map _internalMarshall($tied_obj->[0]->{$_->{key}}, $defs_store), @{$tied_obj->[1]->{FieldsOrder}};
  2         15  
156 2         12 my $m = ":" . length($class_name) . ":" . $class_name . join('', @list);
157 2         16 return "\@SDT/%:" . length($m) . ":" . $m;
158             }
159            
160             sub _create_class_nametag {
161 14     14   17 my ($nametag) = @_;
162 14         61 return ":" . length($nametag) . ":" . $nametag;
163             }
164            
165             sub _create_class_field {
166 6     6   14 my ($field) = @_;
167 6         9 my @list;
168 6         11 foreach my $field_name (qw{display-name key short}) {
169 18 100       46 next unless exists $field->{$field_name};
170 12         22 push @list, _create_class_nametag($field_name) . _internalMarshallSimpleScalar($field->{$field_name});
171             }
172 6         15 my $m = join("", @list);
173 6         58 return "\@SDT/{:" . length($m) . ":" . $m;
174             }
175            
176             sub _create_class_definition {
177 2     2   4 my ($class_name, $record) = @_;
178 2         3 my @keys_list = map _create_class_field($_), @{$record->{FieldsOrder}};
  2         10  
179 2         7 my $keys_joined = join '', @keys_list;
180 2         9 my $keys_marshalled = "\@SDT/[" . scalar(@keys_list) . ":" . length($keys_joined) . ":" . $keys_joined;
181 2         42 my $name_marshalled = _internalMarshall($class_name, {});
182 2         9 my $joined1 = ":4:keys" . $keys_marshalled . ":4:name" . $name_marshalled;
183 2         6 return _create_class_nametag($class_name) . "\@SDT/{:" . length($joined1) . ":" . $joined1;
184             }
185            
186             sub Marshall {
187 3     3 1 394 my @params = @_;
188 3         13 my $class_def = {};
189 3         5 my $data;
190 3 50       38 die "Please call marshall with at least one data\n" if (@params < 1);
191 3 50       9 if (@params == 1) {
192 3         11 $data = _internalMarshall($params[0], $class_def);
193             } else {
194 0         0 $data = _internalMarshall(\@params, $class_def);
195             }
196             # if no class was invovled, return the data itself.
197             #return $data unless %$class_def;
198 3         6 my $serialize_classes;
199 3 100       9 if (not %$class_def) {
200             # when we have no class - add empty class data
201             # wasting bytes is fun.
202 1         2 $serialize_classes = '@SDT/{:26::13:map-class-map@SDT/{:0:';
203             } else {
204 2         2 my @list;
205 2         12 while (my ($key, $record) = each %$class_def) {
206 2         9 push @list, _create_class_definition($key, $record);
207             }
208 2         5 my $class_data_classes = join('', @list);
209 2         8 my $maped2 = "\@SDT/{:" . length($class_data_classes) . ":" . $class_data_classes;
210 2         6 my $joined3 = ":13:map-class-map" . $maped2;
211 2         9 $serialize_classes = "\@SDT/{:" . length($joined3) . ":" . $joined3;
212             }
213            
214 3         8 my $total_data = $serialize_classes . $data;
215 3         33 return "\@SDT/*:" . length($total_data) . ":" . $total_data;
216             }
217            
218             sub _unmarshallClassDef_keydef {
219 6     6   8 my ($string_ref, $pos_ref) = @_;
220 6         24 my ($prefix, $len1) = $$string_ref =~ /^(\@SDT\/{:(\d+):)/;
221 6 50       15 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
222             unless defined $len1;
223 6         8 substr($$string_ref, 0, length($prefix), '');
224 6         7 $$pos_ref += length($prefix);
225            
226 6         12 my $my_string = substr($$string_ref, 0, $len1);
227 6         7 my %key_def;
228 6         18 while ($my_string) {
229 12         42 my ($p_name_len) = $my_string =~ /^:(\d+):/;
230 12 50 33     54 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
231             unless (defined $p_name_len) and (length($my_string) >= $p_name_len + length($p_name_len) + 2);
232 12         23 substr($my_string, 0, 2 + length($p_name_len), '');
233 12         17 my $p_name = substr($my_string, 0, $p_name_len, '');
234 12         17 $$pos_ref += $p_name_len + 2 + length($p_name_len);
235            
236 12         41 my ($prefix2, $p_value_len) = $my_string =~ /^(\@SDT\/\$S:(\d+):)/;
237 12 50 33     54 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
238             unless (defined $p_value_len) and (length($my_string) >= $p_value_len + length($prefix2));
239 12         17 substr($my_string, 0, length($prefix2), '');
240 12         17 my $p_value = substr($my_string, 0, $p_value_len, '');
241 12         14 $$pos_ref += $p_value_len + length($prefix2);
242            
243 12         36 $key_def{$p_name} = $p_value;
244             }
245            
246 6         9 substr($$string_ref, 0, $len1, '');
247 6         7 $$pos_ref += $len1;
248 6         13 return \%key_def;
249             }
250            
251             sub _unmarshallClassDef {
252 2     2   3 my ($string_ref, $pos_ref, $class_storage) = @_;
253 2         9 my ($len1) = $$string_ref =~ /^:(\d+):/;
254 2 50       6 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
255             unless defined $len1;
256            
257 2         10 substr($$string_ref, 0, 2 + length($len1), '');
258 2         5 my $slashed_name = substr($$string_ref, 0, $len1, '');
259 2         3 $$pos_ref += $len1 + length($len1) + 2;
260            
261 2         12 my ($prefix, $num_of_keys) = $$string_ref =~ /^(\@SDT\/{:\d+::4:keys\@SDT\/\[(\d+):\d+:)/;
262 2 50       12 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
263             unless defined $prefix;
264 2         3 substr($$string_ref, 0, length($prefix), '');
265 2         3 $$pos_ref += length($prefix);
266            
267 2         3 my @keys_defs;
268 2         6 for (1..$num_of_keys) {
269 6         12 my $key_def = _unmarshallClassDef_keydef($string_ref, $pos_ref);
270 6         13 push @keys_defs, $key_def;
271             }
272 2         5 my %fields = map { ( $_->{key}, $_ ) } @keys_defs;
  6         16  
273 2         12 my ($postfix, $len2) = $$string_ref =~ /^(:4:name\@SDT\/\$S:(\d+):)/;
274 2 50       7 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
275             unless defined $postfix;
276 2         4 substr($$string_ref, 0, length($postfix) + $len2, '');
277 2         3 $$pos_ref += length($postfix) + $len2;
278            
279 2         18 my $class_def = {
280             FieldsDefs => \%fields,
281             FieldsOrder => \@keys_defs,
282             PackageName => '', # incoming class - no package associated.
283             SlashedName => $slashed_name,
284             Final => 1,
285             };
286 2         17 $class_storage->{$slashed_name} = $class_def;
287             }
288            
289             sub _internalUnmarshall {
290 22     22   31 my ($string_ref, $pos_ref, $class_storage) = @_;
291            
292 22         97 my ($type, $typeInfo, $len) = $$string_ref =~ /^\@SDT\/(\{|\[|\$|\*|\%)([^:]*):(\d*):/;
293 22 50       49 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
294             unless $type;
295            
296             {
297             # remove the already processed prefix
298 22         24 my $second_colon = length($typeInfo) + length($len) + 7;
  22         34  
299 22         23 my $length_handled = $second_colon + 1;
300 22         30 substr($$string_ref, 0, $length_handled, '');
301 22         32 $$pos_ref += $length_handled;
302             }
303 22 50       43 $len = 0 unless $len;
304            
305 22 100       58 if ($type eq '$') {
    100          
    50          
    100          
    50          
306 16 50       38 if ($typeInfo eq '0') {
    50          
307 0         0 return undef;
308             } elsif ($typeInfo eq 'S') {
309 16         31 my $ret_string = substr($$string_ref, 0, $len, '');
310 16         42 $$pos_ref += $len;
311 16         41 return $ret_string;
312             } else {
313 0         0 die "Failed parsing string at " . $$pos_ref . " near " . substr($$string_ref, 0, 5);
314             }
315             } elsif ($type eq '[') {
316             # @SDT/[::...
317 1 50       6 die "Not a STAF data. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
318             unless $typeInfo =~ /\d+/;
319 1         2 my @list;
320 1         5 for (1..$typeInfo) {
321 10         29 push @list, _internalUnmarshall($string_ref, $pos_ref, $class_storage);
322             }
323 1         6 return \@list;
324             } elsif ($type eq '{') {
325             # @SDT/{:::
326             # ...
327             # ::
328 0 0       0 if ($len == 0) {
329             # handle an empty map
330 0         0 return {};
331             }
332 0         0 my $the_rest = ":" . substr($$string_ref, 0, $len);
333 0         0 my %map;
334 0         0 while ($the_rest) {
335 0 0       0 die "Failed parsing string at " . $$pos_ref . " near " . substr($the_rest, 0, 5)
336             unless substr($the_rest, 0, 1) eq ':';
337 0         0 my $next_colon = index($the_rest, ':', 1);
338 0 0 0     0 die "Failed parsing string at " . $$pos_ref . " near " . substr($the_rest, 0, 5)
339             unless $next_colon > 1 and $next_colon < 8;
340 0         0 my $key_len = substr($the_rest, 1, $next_colon - 2);
341 0         0 my $key_name = substr($the_rest, $next_colon+1, $key_len);
342 0         0 my $handled = $next_colon + 1 + $key_len;
343 0         0 $$pos_ref += $handled;
344 0         0 substr($the_rest, 0, $handled, '');
345 0         0 my $value = _internalUnmarshall(\$the_rest, $pos_ref, $class_storage);
346 0         0 $map{$key_name} = $value;
347             }
348 0         0 substr($$string_ref, 0, $len, '');
349 0         0 return \%map;
350             } elsif ($type eq '%') {
351             #@SDT/%::::
352             #
353             # ...
354             #
355 2         7 my ($name_len) = $$string_ref =~ /^:(\d+):/;
356 2         5 my $class_name = substr($$string_ref, 2 + length($name_len), $name_len);
357 2 50       20 die "Not a STAF data - unrecognized class. at " . $$pos_ref . " near " . substr($$string_ref, 0, 10)
358             unless exists $class_storage->{$class_name};
359 2         4 my $class_data = $class_storage->{$class_name};
360             # remove the class name from the string
361 2         4 my $handled = 2 + length($name_len) + $name_len;
362 2         4 substr($$string_ref, 0, $handled, '');
363 2         3 $$pos_ref += $handled;
364             # create class instance
365 2         3 my %object;
366 2         14 tie %object, 'Class::STAF::Marshalled::_Tied', $class_data;
367 2         3 foreach my $field_record (@{ $class_data->{FieldsOrder} }) {
  2         5  
368 6         12 my $value = _internalUnmarshall($string_ref, $pos_ref, $class_storage);
369 6         27 $object{$field_record->{key}} = $value;
370             }
371 2         4 my $object_ref = \%object;
372 2         10 return bless $object_ref, 'Class::STAF::Marshalled';
373             } elsif ($type eq '*') {
374 3         16 my ($len1, $len2) = $$string_ref =~ /^\@SDT\/{:(\d+)::13:map-class-map\@SDT\/{:(\d+):/;
375 3         6 my $prefix_len = length("\@SDT\/{:::13:map-class-map\@SDT\/{::") + length($len1) + length($len2);
376 3         5 substr($$string_ref, 0, $prefix_len, '');
377 3         4 $$pos_ref += $prefix_len;
378 3         19 my $classes_raw_string = substr($$string_ref, 0, $len2, '');
379 3         7 while ($classes_raw_string) {
380 2         7 _unmarshallClassDef(\$classes_raw_string, $pos_ref, $class_storage);
381             }
382 3         21 return _internalUnmarshall($string_ref, $pos_ref, $class_storage);
383             }
384             }
385            
386             sub UnMarshall {
387 3     3 1 259 my $string = shift;
388 3 50 33     22 return undef if (!defined $string) or ($string !~ /^\@SDT\//);
389 3         4 my $current_pos = 0;
390 3         4 my $ret_data;
391 3         4 eval {
392 3         10 $ret_data = _internalUnmarshall(\$string, \$current_pos, {});
393             };
394 3 50       12 print $@ if $@;
395 3 50       7 print "Error: not all data was parsed: |", $string, "|\n"
396             if $string;
397 3         9 return $ret_data;
398             }
399            
400             sub get_staf_class_name {
401 0     0 1 0 my $ref = shift;
402 0 0       0 if (not defined $ref) {
403 0         0 die "usage: get_staf_class_name(\$ref)";
404             }
405 0 0       0 return unless UNIVERSAL::isa($ref, "HASH"); # a class have to be a hash ref
406 0         0 my $tied_obj = tied(%$ref);
407 0 0       0 return unless $tied_obj; # and a tied object
408 0 0       0 return unless UNIVERSAL::isa($tied_obj, 'Class::STAF::Marshalled::_Tied');
409 0         0 return scalar($tied_obj->[1]->{SlashedName});
410             }
411            
412             sub get_staf_fields {
413 0     0 1 0 my $ref = shift;
414 0 0       0 if (not defined $ref) {
415 0         0 die "usage: get_staf_fields(\$ref)";
416             }
417 0 0       0 return unless UNIVERSAL::isa($ref, "HASH"); # a class have to be a hash ref
418 0         0 my $tied_obj = tied(%$ref);
419 0 0       0 return unless $tied_obj; # and a tied object
420 0 0       0 return unless UNIVERSAL::isa($tied_obj, 'Class::STAF::Marshalled::_Tied');
421 0         0 my @fields = map { +{ %$_ } } @{ $tied_obj->[1]->{FieldsOrder} };
  0         0  
  0         0  
422 0         0 return @fields;
423             }
424            
425             package # hide?
426             Class::STAF::Marshalled::_Tied;
427            
428             # Each Tied object is a blessed array ref, that contain two items:
429             # [0] - hash ref with all the fields pre-defined
430             # [1] - a reference to the class definition, sent from STAF::Marshalled
431             sub TIEHASH {
432 3     3   8 my ($class, $package_store) = @_;
433 3         4 my %values;
434 3         6 while (my ($key, $val) = each %{$package_store->{FieldsDefs}}) {
  12         39  
435 9 50       17 if (exists $val->{default}) {
436 0         0 $values{$key} = $val->{default};
437             } else {
438 9         17 $values{$key} = undef;
439             }
440             }
441 3         7 my $self = [\%values, $package_store];
442 3         12 return bless $self, $class;
443             }
444            
445             sub FETCH {
446 3     3   16 my ($self, $key) = @_;
447 3 50       9 die "Key $key does not exists\n" unless exists $self->[0]->{$key};
448 3         8 return $self->[0]->{$key};
449             }
450            
451             sub STORE {
452 9     9   20 my ($self, $key, $value) = @_;
453 9 50       35 die "Key $key does not exist\n" unless exists $self->[0]->{$key};
454 9         35 $self->[0]->{$key} = $value;
455             }
456            
457             sub DELETE {
458 0     0   0 my ($self, $key) = @_;
459 0         0 die "Deleting keys from an Object does not make sense\n";
460             }
461            
462             sub CLEAR {
463 0     0   0 my ($self) = @_;
464 0         0 while (my ($key, $val) = each %{$self->{fields_defs}}) {
  0         0  
465 0 0       0 if (exists $val->{default}) {
466 0         0 $self->[0]->{$key} = $val->{default};
467             } else {
468 0         0 $self->[0]->{$key} = undef;
469             }
470             }
471             }
472            
473             sub EXISTS {
474 3     3   22 my ($self, $key) = @_;
475 3         10 return exists $self->[0]->{$key};
476             }
477            
478             sub FIRSTKEY {
479 1     1   14 my ($self) = @_;
480 1         2 return each %{$self->[0]};
  1         5  
481             }
482            
483             sub NEXTKEY {
484 3     3   4 my ($self, $last_key) = @_;
485 3         4 return each %{$self->[0]};
  3         10  
486             }
487            
488             sub SCALAR {
489 0     0     my ($self) = @_;
490 0           return scalar %{$self->[0]};
  0            
491             }
492            
493             1;
494            
495             __END__