/usr/local/CPAN/CPANXR/CPANXR/Visualize/Graph.pm


# $Id: Graph.pm,v 1.12 2003/10/05 09:34:24 clajac Exp $

package CPANXR::Visualize::Graph;
use CPANXR::Database;
use CPANXR::Parser qw(:constants);
use GraphViz;
use strict;

sub none {
  my $graph = GraphViz->new();
  $graph->add_node('Don\'t know\nwhat to do!');
  return $graph;
}

# IS-A relationships
sub class {
  my ($self, $class) = @_;

  $self = bless { checked => {} }, $self;

  my $graph = GraphViz->new(
			    width => 6,
			    height => 6,
			    directed => 1,
			    rankdir => 1,
			   );

  my $symbol = CPANXR::Database->select_symbol($class)->[0]->[0];
  $graph->add_node($class, label => $symbol, shape => 'rect');

  $self->_class_subclasses($graph, $class);

  return $graph;
}

sub _class_subclasses {
  my ($self, $graph, $parent) = @_;
  
  return if(exists $self->{checked}->{$parent});

  my $sub = CPANXR::Database->select_connections(symbol_id => $parent, limit_types => [CONN_ISA]);

  for (@$sub) {
    my $symbol = CPANXR::Database->select_symbol($_->[8]);
    $graph->add_node($_->[8], label => $symbol->[0]->[0], shape => 'rect');
    $graph->add_edge($parent => $_->[8], dir => 'back', label => 'is-a');
    $self->_class_subclasses($graph, $_->[8]);
  }

  1;
}

# Files
sub file {
  my ($self, $file_id) = @_;

  my $graph = GraphViz->new(
			    width => 6,
			    height => 6,
			    directed => 1,
			    rankdir => 1,
			   );

  my $result = CPANXR::Database->select_connections(file_id => $file_id);

  return $graph unless @$result;

  my $file = $result->[0]->[4];
 
  my %sym_list = map { $_->[0] => $_->[1] } grep { $_->[7] != CONN_DECL } @$result;
  my %added;
  my $cluster = _file_get_cluster($result->[0]->[9]);
  $file =~ s/^$cluster\/// if $cluster;

  $graph->add_node($file, shape => 'rect', style=>'filled', fillcolor => '#eeeeee', URL => "show?id=$result->[0]->[5]", cluster => $cluster);

  #  Packages
  {
    my %packages = map { $_->[1] => $_->[0] } grep { $_->[7] == CONN_PACKAGE } @$result;
    while (my ($name, $id) = each %packages) {
      my %sub = map { $_->[1] => 1 } grep { $_->[7] == CONN_DECL && $_->[6] == $id } @$result;
      $graph->add_node($id, label => [$name, join('\n', keys %sub)], URL => "find?symbol=$id", style => 'filled', fillcolor => '#ffffcc');
      $graph->add_edge($file => $id, label => 'declares');
      $added{$id} = 1;

      # Find users
      my $users = CPANXR::Database->select_connections(package_id => $id);
      my %files = map { $_->[4] => [$_->[5], $_->[9]] } @$users;

      for my $user_file (keys %files) {
	my $cluster = _file_get_cluster($files{$user_file}->[1]);
	$user_file =~ s/^$cluster\///;
	next if $user_file eq $file;
	my $url = "show?id=$files{$user_file}->[0]";

	$graph->add_node($user_file, shape => 'rect', style => 'filled', fillcolor => '#eeeeee', URL => $url, cluster => $cluster);
	$graph->add_edge($user_file => $id, label => 'references', URL => $url);
      }
    }
  }

  # Includes
  {
    my %includes = map { $_->[1] => $_->[0] } grep { $_->[7] == CONN_INCLUDE } @$result;
    while (my ($name, $id) = each %includes) {
      my %ref = map { $_->[1] => 1 } grep { $_->[6] == $id } @$result;
      my %attr;
      
      if (%ref && $name !~ /^base|vars$/) {
	$attr{label} = [$name, join('\n', keys %ref)];
      } else {
	$attr{label} = $name;
	$attr{shape} = 'rect';
      }
      $graph->add_node($id, %attr, URL => "find?symbol=$id");
      $added{$id} = 1;
    }
  }

  # edge
  {
    my %link;
    for my $ref (@$result) {
      my $url = "show?id=" . $file_id . "&hl=" . $ref->[2] . "#l" . $ref->[2];
      if ($ref->[7] == CONN_ISA) {
	unless ($added{$ref->[0]}) {
	  $graph->add_node($ref->[0], shape => 'rect', label => $sym_list{$ref->[0]});
	  $added{$ref->[0]} = 1;
	}

	$graph->add_edge($ref->[0] => $ref->[8], label => 'is-a', URL => $url);
      } elsif ($ref->[7] == CONN_INCLUDE) {
	next if exists $link{"$ref->[8]:$ref->[0]"};
	unless ($added{$ref->[8]}) {
	  $graph->add_node($ref->[8], shape => 'rect', label => $sym_list{$ref->[8]});
	  $added{$ref->[8]} = 1;
	}

	$graph->add_edge($ref->[8] => $ref->[0], label => 'uses', URL => $url);
	$link{"$ref->[8]:$ref->[0]"} = 1;
      }
    }
  }

  return $graph;
}

my %Clusters;
sub _file_get_cluster {
  my $id = shift;
  return $Clusters{$id} if exists $Clusters{$id};
  my $dist = CPANXR::Database->select_distributions(id => $id);
  if (@$dist) {
    $Clusters{$dist->[0]->[0]} = $dist->[0]->[1];
    return $dist->[0]->[1];
  }

  return "";
}

# Subroutine flow
sub subroutine {
  my ($self, $id) = @_;

  my ($sub_id, $pkg_id) = split/_/,$id,2;

  $self = bless { checked => {}, nodes => {} }, $self;

  my $graph = GraphViz->new(
			    width => 6,
			    height => 6,
			    directed => 1,
			    rankdir => 1,
			   );

  my $sub_name = CPANXR::Database->select_symbol($sub_id)->[0]->[0];
  my $pkg_name = CPANXR::Database->select_symbol($pkg_id)->[0]->[0];

  # Add from node
  $graph->add_node($sub_id,
		   label => $sub_name, 
		   cluster => $pkg_name, 
		   shape => 'rect',
		   style => 'filled', fillcolor => '#ffffcc');

  $self->_subroutine_calls($graph, $sub_id, $pkg_id);
  return $graph;
}

sub _subroutine_calls {
  my ($self, $graph, $sub_id, $pkg_id) = @_;

  return if(exists $self->{checked}->{"${sub_id}:${pkg_id}"});
  return unless $sub_id;
  return unless $pkg_id;

  $self->{checked}->{"${sub_id}:${pkg_id}"} = 1;
  # Get calls
  my $calls = CPANXR::Database->select_connections(caller_id => $pkg_id, caller_sub_id => $sub_id);

  for my $call (@$calls) {
    my $url = "graph?sub=" . $call->[0] . "_" . ($call->[6] || $call->[8]);

    my $sub_call_pkg_name = CPANXR::Database->select_symbol($call->[6] || $call->[8])->[0]->[0];
    $graph->add_node($call->[0], label => $call->[1], shape => 'rect', cluster => $sub_call_pkg_name, URL => $url);
    unless(exists $self->{nodes}->{"${sub_id}:$call->[0]"}) {
      $graph->add_edge($sub_id => $call->[0]);
      $self->{nodes}->{"${sub_id}:$call->[0]"} = 1;
      $self->_subroutine_calls($graph, $call->[0], $call->[6] || $call->[8]);
    }
  }  
}

1;