package Analizo::Model;
use strict;
use Graph;
use File::Basename;
sub new {
my @defaults = (
members => {},
modules => {},
files => {},
module_by_file => {},
calls => {},
lines => {},
protection => {},
inheritance => {},
parameters => {},
conditional_paths => {},
abstract_classes => [],
module_names => [],
modules_graph => undef,
files_graph => undef,
);
return bless { @defaults }, __PACKAGE__;
}
sub modules {
my ($self) = @_;
return $self->{modules};
}
sub module_names {
my ($self) = @_;
return @{$self->{module_names}};
}
sub declare_module {
my ($self, $module, $file) = @_;
if (! grep { $_ eq $module} @{$self->{module_names}}) {
push @{$self->{module_names}}, $module;
}
if (defined($file)) {
#undup filename
foreach (@{$self->{files}->{$module}}) {
return if($_ eq $file);
}
$self->{files}->{$module} ||= [];
push(@{$self->{files}->{$module}}, $file);
$self->{module_by_file}->{$file} ||= [];
push @{$self->{module_by_file}->{$file}}, $module;
}
}
sub files {
my ($self, $module) = @_;
return $self->{files}->{$module};
}
sub module_by_file {
my ($self, $file) = @_;
return @{$self->{module_by_file}->{$file} || []};
}
sub inheritance {
my ($self, $module) = @_;
my $list = $self->{inheritance}->{$module};
return $list ? @$list : ();
}
sub add_inheritance {
my ($self, $child, $parent) = @_;
$self->{inheritance}->{$child} = [] if !exists($self->{inheritance}->{$child});
push @{$self->{inheritance}->{$child}}, $parent;
}
sub members {
my ($self) = @_;
return $self->{members};
}
sub declare_member {
my ($self, $module, $member, $type) = @_;
# mapping member to module
$self->{members}->{$member} = $module;
}
sub type {
my ($self, $member) = @_;
return $self->{types}->{$member};
}
sub declare_function {
my ($self, $module, $function) = @_;
return unless $module;
$self->declare_member($module, $function, 'function');
if (!exists($self->{modules}->{$module})){
$self->{modules}->{$module} = {};
$self->{modules}->{$module}->{functions} = [];
}
if(! grep { $_ eq $function } @{$self->{modules}->{$module}->{functions}}){
push @{$self->{modules}->{$module}->{functions}}, $function;
}
}
sub declare_variable {
my ($self, $module, $variable) = @_;
$self->declare_member($module, $variable, 'variable');
if (!exists($self->{modules}->{$module})){
$self->{modules}->{$module} = {};
$self->{modules}->{$module}->{variables} = [];
}
if(! grep { $_ eq $variable } @{$self->{modules}->{$module}->{variables}}){
push @{$self->{modules}->{$module}->{variables}}, $variable;
}
}
sub add_call {
my ($self, $caller, $callee, $reftype) = @_;
$reftype ||= 'direct';
$self->{calls}->{$caller} = {} if !exists($self->{calls}->{$caller});
$self->{calls}->{$caller}->{$callee} = $reftype;
}
sub calls {
my ($self) = @_;
return $self->{calls};
}
sub abstract_classes {
my ($self) = @_;
my $list = $self->{abstract_classes};
return $list ? @$list : ();
}
sub add_abstract_class {
my ($self, $module) = @_;
push @{$self->{abstract_classes}},$module;
}
sub add_variable_use {
add_call(@_, 'variable');
}
sub add_loc {
my ($self, $function, $lines) = @_;
$self->{lines}->{$function} = $lines;
}
sub add_conditional_paths {
my ($self, $function, $conditional_paths) = @_;
$self->{conditional_paths}->{$function} = $conditional_paths;
}
sub add_protection {
my ($self, $member, $protection) = @_;
$self->{protection}->{$member} = $protection if $member;
}
sub add_parameters {
my ($self, $function, $parameters) = @_;
$self->{parameters}->{$function} = $parameters;
}
sub functions {
my ($self, $module) = @_;
my $list = $self->{modules}->{$module}->{functions};
return $list ? @$list : ();
}
sub variables {
my ($self, $module) = @_;
my $list = $self->{modules}->{$module}->{variables};
return $list ? @$list : ();
}
sub all_members {
my ($self, $module) = @_;
my @functions = $self->functions($module);
my @variables = $self->variables($module);
return @functions, @variables;
}
sub _group_files {
my @files = @_;
(my $file = $files[0]) =~ s/\.[^.]+$//;
$file;
}
sub modules_graph {
my ($self) = @_;
$self->build_graphs unless $self->{modules_graph};
return $self->{modules_graph};
}
sub files_graph {
my ($self) = @_;
$self->build_graphs unless $self->{files_graph};
return $self->{files_graph};
}
sub build_graphs {
my ($self) = @_;
$self->{modules_graph} = Graph->new;
$self->{files_graph} = Graph->new;
$self->{modules_graph}->set_graph_attribute('name', 'graph');
$self->{files_graph}->set_graph_attribute('name', 'graph');
$self->_add_all_vertex_on_each_graph;
$self->_add_all_references_between_files_and_modules_as_edges_on_each_graph;
$self->_add_all_references_from_inheritance_as_edges_on_each_graph;
}
sub _add_all_vertex_on_each_graph{
my ($self) = @_;
foreach my $module (keys %{ $self->{files}}) {
# Modules Graph
$self->{modules_graph}->add_vertex($module);
# Files Graph
my $file = $self->files($module);
my $file_without_extension = _group_files(@{ $file });
$self->{files_graph}->add_vertex($file_without_extension);
}
}
sub _add_all_references_between_files_and_modules_as_edges_on_each_graph{
my ($self) = @_;
foreach my $current_function_call (keys %{$self->calls}) {
# Modules Graph
my $calling_module = $self->_function_to_module($current_function_call);
# Files Graph
my $calling_file = $self->_function_to_file($current_function_call);
next unless $calling_file || $calling_module;
if ($calling_module) {
$self->{modules_graph}->add_vertex($calling_module);
}
if ($calling_file) {
$calling_file = _group_files(@{$calling_file});
$self->{files_graph}->add_vertex($calling_file);
}
foreach my $call_inside_current_function (keys %{$self->calls->{$current_function_call}}) {
# Modules Graph
my $called_module = $self->_function_to_module($call_inside_current_function);
# Files Graph
my $called_file = $self->_function_to_file($call_inside_current_function);
next unless $called_module || $called_file;
# Modules Graph
if ($called_module) {
$self->{modules_graph}->add_vertex($called_module);
unless ($calling_module eq $called_module) {
$self->{modules_graph}->add_edge($calling_module, $called_module);
}
}
# Files Graph
if ($called_file) {
$called_file = _group_files(@{$called_file});
$self->{files_graph}->add_vertex($called_file);
unless ($calling_file eq $called_file) {
$self->{files_graph}->add_edge($calling_file, $called_file);
}
}
}
}
}
sub _add_all_references_from_inheritance_as_edges_on_each_graph {
my ($self) = @_;
foreach my $subclass (keys(%{$self->{inheritance}})) {
# Modules Graph
$self->{modules_graph}->add_vertex($subclass);
# Files Graph
my $subclass_file = $self->files($subclass);
if ($subclass_file) {
$subclass_file = _group_files(@{$subclass_file});
$self->{files_graph}->add_vertex($subclass_file);
}
foreach my $superclass ($self->inheritance($subclass)) {
$self->_find_recursively_references_from_deep_inheritance($subclass, $subclass_file, $superclass);
}
}
}
sub _find_recursively_references_from_deep_inheritance {
my ($self, $subclass, $subclass_file, $superclass) = @_;
# Modules Graph
$self->{modules_graph}->add_edge($subclass, $superclass);
# Files Graph
my $superclass_file = $self->files($superclass);
if ($superclass_file && $subclass_file) {
$superclass_file = _group_files(@{$superclass_file});
$self->{files_graph}->add_edge($subclass_file, $superclass_file);
}
foreach my $super_uper_class ($self->inheritance($superclass)) {
$self->_find_recursively_references_from_deep_inheritance($subclass, $subclass_file, $super_uper_class);
}
}
sub _function_to_file {
my ($self, $function) = @_;
return unless exists $self->members->{$function};
my $module = $self->members->{$function};
$self->{files}->{$module};
}
sub _add_dependency {
my ($dependencies, $from, $to) = @_;
$dependencies->{$from} = { } if !exists($dependencies->{$from});
if (exists $dependencies->{$from}->{$to}) {
$dependencies->{$from}->{$to} += 1;
} else {
$dependencies->{$from}->{$to} = 1;
}
}
sub _reftype_to_style {
my ($reftype) = @_;
$reftype = $reftype || 'direct';
my %styles = (
'direct' => 'solid',
'indirect' => 'dotted',
'variable' => 'dashed',
);
return $styles{$reftype} || 'solid';
}
sub callgraph {
my ($self, %args) = @_;
my $graph = Graph->new;
$graph->set_graph_attribute('name', 'callgraph');
if ($args{group_by_module}) {
# listing dependencies grouped by module
my $modules_dependencies = { };
foreach my $caller (sort(keys %{$self->calls})) {
foreach my $callee (sort(keys %{$self->calls->{$caller}})) {
my $calling_module = $self->_function_to_module($caller);
my $called_module = $self->_function_to_module($callee);
next unless (defined($calling_module) && defined($called_module) && ($calling_module ne $called_module));
_add_dependency($modules_dependencies, $calling_module, $called_module);
}
}
foreach my $subclass (sort(keys(%{$self->{inheritance}}))) {
foreach my $superclass ($self->inheritance($subclass)) {
_add_dependency($modules_dependencies, $subclass, $superclass);
}
}
foreach my $calling_module (sort(keys %{$modules_dependencies})) {
foreach my $called_module (sort(keys %{$modules_dependencies->{$calling_module}})) {
my $strength = $modules_dependencies->{$calling_module}->{$called_module};
$graph->add_edge($calling_module, $called_module);
$graph->set_edge_attribute($calling_module, $called_module, 'style', 'solid');
$graph->set_edge_attribute($calling_module, $called_module, 'label', $strength);
}
}
} else {
# listing raw dependency info
foreach my $caller (grep { $self->_include_caller($_, @{$args{omit}}) } sort(keys(%{$self->calls}))) {
foreach my $callee (grep { $self->_include_callee($_, $args{include_externals}, @{$args{omit}}) } sort(keys(%{$self->calls->{$caller}}))) {
my $style = _reftype_to_style($self->calls->{$caller}->{$callee});
$graph->add_edge($caller, $callee);
$graph->set_edge_attribute($caller, $callee, 'style', $style);
$graph->set_vertex_attribute($caller, 'group', $self->_function_to_module($caller));
$graph->set_vertex_attribute($callee, 'group', $self->_function_to_module($callee));
}
}
}
return $graph;
}
sub _file_to_module {
my ($filename) = @_;
$filename =~ s/\.r\d+\.expand$//;
return basename($filename);
}
sub _function_to_module {
my ($self, $function) = @_;
return undef if !exists($self->members->{$function});
return _file_to_module($self->members->{$function});
}
sub _include_caller {
my ($self, $function, @omitted) = @_;
return !grep { $function eq $_ } @omitted;
}
sub _include_callee {
my ($self, $member, $include_externals, @omitted) = @_;
return $self->_include_caller($member, @omitted) && ( exists($self->members->{$member}) || $include_externals );
}
1;