package Xmldoom::Definition::Link; use base qw(Exporter); use strict; use Data::Dumper; our @EXPORT_OK = qw( ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY, MANY_TO_MANY ); our $ONE_TO_ONE = 'one-to-one'; our $MANY_TO_ONE = 'many-to-one'; our $ONE_TO_MANY = 'one-to-many'; our $MANY_TO_MANY = 'many-to-many'; sub new { my $class = shift; my $args = shift; if ( not defined $args ) { $args = [ ]; } elsif ( ref($args) ne 'ARRAY' ) { $args = [ $args ]; } my $relationship; if ( scalar @$args > 1 ) { # TODO: is this really that simple? $relationship = $MANY_TO_MANY; } else { my $fn = $args->[0]; my $local_key = $fn->get_table()->get_column_names({ primary_key => 1 }); my $foreign_key = $fn->get_reference_table()->get_column_names({ primary_key => 1 }); # check if the local or foreign connection has the complete table key my $has_local_key = $fn->is_local_column_names( $local_key ); my $has_foreign_key = $fn->is_foreign_column_names( $foreign_key ); # look-up the appropriate relationship if ( not $has_local_key and $has_foreign_key ) { $relationship = $MANY_TO_ONE; } elsif ( $has_local_key and not $has_foreign_key ) { $relationship = $ONE_TO_MANY; } elsif ( $has_local_key and $has_foreign_key ) { $relationship = $ONE_TO_ONE; } else { $relationship = $MANY_TO_MANY; } } my $self = { foreign_keys => $args, relationship => $relationship }; bless $self, $class; return $self; } sub get_foreign_keys { return shift->{foreign_keys}; } sub get_count { return scalar @{shift->{foreign_keys}}; } sub get_start { return shift->{foreign_keys}->[0]; } sub get_end { return shift->{foreign_keys}->[-1]; } sub get_start_column_names { return shift->get_start()->get_local_column_names(); } sub get_end_column_names { return shift->get_end()->get_foreign_column_names(); } sub get_start_table_name { return shift->get_start()->get_table_name(); } sub get_end_table_name { return shift->get_end()->get_reference_table_name(); } sub get_relationship { return shift->{relationship}; } sub is_start_column_names { my ($self, $column_names) = @_; return $self->get_start()->is_local_column_names( $column_names ); } sub is_end_column_names { my ($self, $column_names) = @_; return $self->get_end()->is_foreign_column_names( $column_names ); } # NOTE: a convenience function for when you *know* it is or can only accept a link # with a single foreign in it. sub get_foreign_key { my $self = shift; if ( $self->get_count() > 1 ) { die "Link contains a chain of foreign keys rather than a single than a single foreign key which is what we expected. Could be a bug in Xmldoom!"; } return $self->get_foreign_keys()->[0]; } # convenience functions that work like get_foreign_key(). sub get_column_names { return shift->get_foreign_key()->get_column_names(); } sub equals { my ($self, $link) = @_; if ( $self->get_count() != $link->get_count() ) { return 0; } for( my $i = 0; $i < $self->get_count(); $i++ ) { if ( not $self->{foreign_keys}->[$i]->equals( $link->{foreign_keys}->[$i] ) ) { return 0; } } return 1; } sub contains { my ($self, $link) = @_; if ( $link->get_count() > $self->get_count() ) { return 0; } my $offset = ($self->get_count() - $self->get_count()); # attempt to locate the correct offset while ( $offset >= 0 ) { if ( $self->{foreign_keys}->[$offset]->equals( $link->{foreign_keys}->[0] ) ) { # this is it, yo! last; } $offset --; } # couldn't locate the appropriate offset, so it couldn't possibly contain it. if ( $offset < 0 ) { return 0; } # count up from the offset and make sure the rest is equal for( my $i = 1; $i < $link->get_count(); $i++ ) { if ( not $self->{foreign_keys}->[$i+$offset]->equals( $link->{foreign_keys}->[$i] ) ) { return 0; } } return 1; } # this is a static function. sub reduce_longest { my $link_list = shift; my @longest; foreach my $link1 ( @$link_list ) { my $is_contained = 0; foreach my $link2 ( @$link_list ) { if ( $link1 == $link2 ) { next; } if ( $link2->contains( $link1 ) ) { $is_contained = 1; last; } } if ( not $is_contained ) { push @longest, $link1; } } return \@longest; } # this is a static function. sub reduce_shortest { my $link_list = shift; my @shortest; foreach my $link1 ( @$link_list ) { my $is_container = 0; foreach my $link2 ( @$link_list ) { if ( $link1 == $link2 ) { next; } if ( $link1->contains( $link2 ) ) { $is_container = 1; last; } } if ( not $is_container ) { push @shortest, $link1; } } return \@shortest; } sub clone_reverse { my $self = shift; my @foreign_keys; foreach my $fkey ( @{$self->{foreign_keys}} ) { unshift @foreign_keys, $fkey->clone_reverse(); } return Xmldoom::Definition::Link->new( \@foreign_keys ); } 1;