File Coverage

blib/lib/UML/Sequence.pm
Criterion Covered Total %
statement 112 146 76.7
branch 24 50 48.0
condition 2 6 33.3
subroutine 8 12 66.6
pod 0 3 0.0
total 146 217 67.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             UML::Sequence - Render UML sequence diagrams, often by running the code.
4              
5             =head1 SYNOPSIS
6              
7             use UML::Sequence;
8              
9             my $tree = UML::Sequence->new(\@methods, \@outline, \&parse_method);
10             print $tree->build_xml_sequence('Title');
11              
12             =head1 DESCRIPTION
13              
14             To use this package, or see how to use it, see L and
15             L.
16              
17             This class helps produce UML sequence diagrams. build_xml_sequence
18             returns a string (suitable for printing to a file) which the seq2svg.pl
19             script converts into svg.
20              
21             To control the appearance of the sequence diagram, pass to the constructor:
22              
23             1 a reference to an array containing the signatures you want to hear about
24             or a reference to a hash whose keys are the signatures you want
25             2 a reference to an array containing the lines in the outline of calls
26             3 a reference to a sub which takes signatures and returns class and method
27             names
28              
29             To build the array references and supply the code reference consult
30             UML::Sequence::SimpleSeq, UML::Sequence::JavaSeq, or UML::Sequence::PerlSeq.
31             To see one way to call these look in the supplied genericseq script.
32              
33             =head2 EXPORT
34              
35             None, this module is object oriented.
36              
37             =cut
38              
39             package UML::Sequence;
40              
41             require 5.005_62;
42 4     4   2263 use strict;
  4         8  
  4         116  
43 4     4   21 use warnings;
  4         6  
  4         142  
44              
45             our $VERSION = '0.08';
46              
47 4     4   2137 use UML::Sequence::Activation;
  4         12  
  4         7417  
48              
49             sub new {
50 4     4 0 45 my $class = shift; # standard
51 4         7 my $methods_to_include = shift; # array or hash of methods you want to see
52 4         6 my $input = shift; # the outline of calls
53 4         7 my $parse_signature = shift; # code ref which returns class and method
54 4         7 my $grab_methods = shift; # coderef to return method name
55              
56 4         29 my $methods_hash;
57 4 50       18 if (ref($methods_to_include) =~ /ARRAY/) {
58 0         0 $methods_hash = _build_methods_hash($methods_to_include);
59             }
60             else {
61 4         6 $methods_hash = $methods_to_include;
62             }
63              
64 4         9 my $stack = [];
65 4         23 my $root = {
66             LEVEL => -1,
67             DATA => [],
68             NAME => scalar &$parse_signature($input->[0]),
69             INPUT => $input->[0],
70             DISCARD => 0,
71             };
72              
73 4         39 shift @$input;
74              
75 4         10 my $self = {};
76 4         8 $self->{TREE} = $root;
77 4         8 $self->{STACK} = $stack;
78 4         7 $self->{INCLUDE} = $methods_hash;
79 4         7 $self->{SIGPARSE} = $parse_signature;
80 4         8 $self->{GRABMETHODS} = $grab_methods;
81 4         10 bless $self, $class;
82              
83 4         8 push @$stack, $root;
84              
85             # 2.
86 4         8 foreach (@$input) {
87 52         57 my $input_line = $_;
88 52         49 my $depth;
89              
90 52 50       209 $depth = ($input_line =~ s/^(\s+)//) ? length($1) : 0;
91 52         113 $self->_update_stack($input_line, $depth);
92             }
93 4         12 return $self;
94             }
95              
96             sub _grab_outline_text {
97             # 1a.
98 0     0   0 _run_dprof(@_);
99              
100             # 1b.
101 0         0 my $input = _read_dprofpp();
102             # The next line uses the sample data in __DATA__ see the comment there
103             # my $input = _read_sample();
104 0         0 return $input;
105             }
106              
107             sub _build_methods_hash {
108 0     0   0 my $methods_list = shift;
109 0         0 my %methods_hash;
110              
111 0         0 foreach my $method (@$methods_list) {
112 0         0 $methods_hash{$method}++;
113             }
114 0         0 return \%methods_hash;
115             }
116              
117             #sub _read_sample {
118             # my @retval = map { chomp $_; $_; } ;
119             # return \@retval;
120             #}
121              
122             sub _update_stack {
123 52     52   58 my $self = shift;
124 52         55 my $method = shift;
125 52         54 my $level = shift;
126              
127 52         165 my $new_node = {
128             LEVEL => $level,
129             DATA => [],
130             NAME => $method,
131             #
132             # DAA save original input line, which may contain
133             # extra stuff
134             #
135             INPUT => $method,
136             # DISCARD => 0,
137             };
138              
139 52         166 pop @{$self->{STACK}}
  48         136  
140             while ($level <= $self->{STACK}[-1]{LEVEL});
141              
142 52         95 $new_node->{DISCARD} = $self->{STACK}[-1]{DISCARD};
143 52 100       125 unless (defined($self->{INCLUDE}{$method})) {
144             #
145             # the line may have magic, try to capture the extra stuff
146             #
147 10         34 my $methods = $self->{GRABMETHODS}->([ $method ]);
148              
149 10         15 my @methods;
150 10 50       22 if ( ref( $methods ) eq 'ARRAY' ) {
151 0         0 @methods = @{ $methods };
  0         0  
152             }
153             else {
154 10         20 @methods = keys %$methods;
155             }
156              
157 10         14 $method = shift @methods;
158 10   33     32 $new_node->{DISCARD} = ($method && $self->{INCLUDE}{$method});
159             }
160              
161 52         48 push @{$self->{STACK}[-1]{DATA}}, $new_node;
  52         99  
162 52         60 push @{$self->{STACK}}, $new_node;
  52         119  
163              
164             }
165              
166             sub print_tree {
167 0     0 0 0 my $self = shift;
168              
169 0         0 return _print_tree($self->{TREE}, "");
170             }
171              
172             sub _print_tree {
173 0     0   0 my $root = shift;
174 0         0 my $indent = shift;
175 0         0 my $retval;
176              
177 0 0       0 return unless defined $root; # recursion base
178 0 0       0 return if ($root->{DISCARD});
179              
180 0         0 $retval = "$indent$root->{NAME}\n";
181              
182 0         0 foreach my $child (@{$root->{DATA}}) {
  0         0  
183 0         0 my $child_output = _print_tree($child, "$indent ");
184 0 0       0 $retval .= $child_output if $child_output;
185             }
186 0         0 return $retval;
187             }
188              
189             sub build_xml_sequence {
190 1     1 0 7 my $self = shift;
191 1         3 my $title = shift;
192              
193 1         2 $self->{ARROW_NUM} = 0;
194 1         2 $self->{ARROW_LIST} = "\n";
195              
196 1         5 $self->_build_xml_sequence($self->{TREE});
197 1         3 $self->{ARROW_LIST} .= "\n";
198              
199 1         5 $self->_build_class_list();
200 1 50       3 if ($title) {
201 1         55 return "\n\n"
202             . "$self->{CLASS_LIST}\n"
203             . "$self->{ARROW_LIST}\n";
204             }
205             else {
206 0         0 return "\n\n$self->{CLASS_LIST}\n"
207             . "$self->{ARROW_LIST}\n";
208             }
209             }
210              
211             sub _build_xml_sequence {
212 13     13   17 my $self = shift;
213 13         14 my $root = shift; # you must pass this in, $self->{TREE} never changes
214 13         13 my $hasreturn = shift;
215              
216             # recursion bases
217 13 50       27 return unless defined $root;
218 13 50       31 return if $root->{DISCARD};
219 13         17 my $root_call = $root->{NAME};
220 13 50       24 return unless defined $root_call;
221              
222 13         40 my $class = $self->{SIGPARSE}($root_call);
223             # put into to class list, if it isn't already there
224              
225 13 100       43 push @{$self->{CLASSES}}, $class
  4         9  
226             unless defined $self->{ACTIVATIONS}{$class};
227              
228             # create activation and add it to the list for this class
229 13         45 my $activation = UML::Sequence::Activation->new();
230 13         38 $activation->starts($self->{ARROW_NUM});
231 13         52 my $offset = UML::Sequence::Activation
232             ->find_offset($self->{ACTIVATIONS}{$class});
233 13         44 $activation->offset($offset);
234              
235 13         14 push @{$self->{ACTIVATIONS}{$class}}, $activation;
  13         36  
236 13         18 my $asyncs = 0;
237             # visit children
238 13         12 foreach my $child (@{$root->{DATA}}) {
  13         30  
239 12 50       28 next if $child->{DISCARD};
240             #
241             # DAA updated to report returnlist, iterator, conditional, urgency,
242             # and annotation
243             #
244 12         39 my ($child_class, $method, $returns, $iterator, $urgent, $condition,
245             $annot) =
246             $self->{SIGPARSE}($child->{INPUT});
247              
248 12 50       69 my $child_offset =
249             UML::Sequence::Activation
250             ->find_offset($self->{ACTIVATIONS}{$child_class})
251             unless ($child_class eq '_EXTERNAL');
252              
253             #
254             # DAA add pending annotation
255             #
256 12         18 my $closetag = "/>\n";
257             #
258             # until we figure out how to use CDATA and a text element w/
259             # XML::DOM, we'll have to force dquotes to squotes
260             #
261 12 50       23 $annot=~s/"/'/g,
262             $closetag =
263             ">\n\n\n",
264             $annot = undef
265             if $annot;
266              
267 12         16 $self->{ARROW_NUM}++;
268 12         55 $method=~s/\s+$//;
269 12 50       28 $method .= ' !' if $urgent;
270 12 50       22 $method = '* ' . $method if $iterator;
271 12 50       24 $method = "$condition $method" if $condition;
272 12 50       20 my $type = ($child_class eq '_EXTERNAL') ? 'async' : 'call';
273 12 50       27 $asyncs++ if ($type eq 'async');
274 12 50       70 $self->{ARROW_LIST} .= ($type eq 'async') ?
275             "
276             from-offset='$offset' to-offset='$offset' $closetag" :
277              
278             "
279             from-offset='$offset' to-offset='$child_offset' $closetag";
280             #
281             # recurse to handle called class/method
282             #
283 12 50       62 $self->_build_xml_sequence($child, $returns)
284             unless ($type eq 'async');
285             #
286             # DAA add return values if any
287             #
288 12 50       35 $self->{ARROW_LIST} .=
289             "
290             from-offset='$child_offset' to-offset='$offset' />\n"
291             if $returns;
292             }
293              
294 13 50       29 $self->{ARROW_NUM}++
295             if $hasreturn;
296              
297 13         42 $activation->ends($self->{ARROW_NUM});
298             #
299             # if outermost, and it had an external, add external class
300             # to output
301             #
302 13 50 33     45 if ($asyncs && ($self->{TREE}{NAME} eq $root->{NAME})) {
303 0         0 unshift @{$self->{CLASSES}}, '_EXTERNAL';
  0         0  
304             # create activation and add it to the list for this class
305 0         0 my $activation = UML::Sequence::Activation->new();
306 0         0 $activation->starts(0);
307 0         0 my $offset = UML::Sequence::Activation
308             ->find_offset($self->{ACTIVATIONS}{_EXTERNAL});
309 0         0 $activation->offset($offset);
310 0         0 $activation->ends($self->{ARROW_NUM});
311              
312 0         0 push @{$self->{ACTIVATIONS}{_EXTERNAL}}, $activation;
  0         0  
313             }
314             }
315              
316             sub _build_class_list {
317 1     1   3 my $self = shift;
318 1         2 $self->{CLASS_LIST} = "\n";
319              
320 1         15 foreach my $class (@{$self->{CLASSES}}) {
  1         4  
321 4         17 my ($starts, $ends) =
322             UML::Sequence::Activation
323             ->find_bounds($self->{ACTIVATIONS}{$class});
324 4         18 $self->{CLASS_LIST} .=
325             " \n" .
326             " \n";
327              
328 4         6 foreach my $activation (@{$self->{ACTIVATIONS}{$class}}) {
  4         9  
329 13         33 my $act_start = $activation->starts();
330 13         32 my $act_end = $activation->ends();
331 13         32 my $act_offset = $activation->offset();
332 13         55 $self->{CLASS_LIST} .=
333             "
334             "offset='$act_offset' />\n";
335             }
336 4         12 $self->{CLASS_LIST} .= " \n \n";
337             }
338 1         4 $self->{CLASS_LIST} .= "\n";
339             }
340              
341             1;
342              
343             =head1 AUTHOR
344              
345             Phil Crow,
346             Version 0.06 updates by Dean Arnold,
347              
348             =head1 SEE ALSO
349              
350             L
351             L
352             L
353              
354             =head1 COPYRIGHT
355              
356             Copyright(C) 2003-2006, Philip Crow, all rights reserved.
357              
358             You may modify and/or redistribute this code in the same manner as
359             Perl itself.
360              
361             =cut
362              
363             # This data is a small subset of a typical dprofpp -T output.
364             # It's used by _read_sample so you can debug with a small input set.
365             # Use _read_sample in place of _read_dprofpp to switch to this set.
366             __DATA__