change identity pools to use KiokuDB::Set
[scpubgit/stemmatology.git] / lib / Text / Tradition / Collation / Reading.pm
CommitLineData
784877d9 1package Text::Tradition::Collation::Reading;
2
8e1394aa 3use Moose;
784877d9 4use MooseX::NonMoose;
1ca1163d 5use KiokuDB::Set;
6use KiokuDB::Util qw/ weak_set /;
784877d9 7
8extends 'Graph::Easy::Node';
9
910a0a6d 10has 'rank' => (
11 is => 'rw',
12 isa => 'Int',
13 predicate => 'has_rank',
14 );
eca16057 15
16has 'is_lacuna' => (
17 is => 'rw',
18 isa => 'Bool',
19 );
784877d9 20
21# This contains an array of reading objects; the array is a pool,
22# shared by the reading objects inside the pool. When a reading is
23# added to the pool, all the same_as attributes should be updated.
24has 'same_as' => (
1ca1163d 25 does => 'KiokuDB::Set',
d047cd52 26 is => 'rw',
d047cd52 27 );
1ca1163d 28
29has 'is_primary' => (
30 is => 'rw',
31 isa => 'Bool',
32 default => 1,
33 );
784877d9 34
8e1394aa 35# Deal with the non-arg option for Graph::Easy's constructor.
36around BUILDARGS => sub {
1ca1163d 37 my $orig = shift;
38 my $class = shift;
39
40 my %args;
41 if( @_ == 1 && ref( $_[0] ) ne 'HASH' ) {
42 return $class->$orig( 'name' => $_[0] );
43 } else {
44 return $class->$orig( @_ );
45 }
8e1394aa 46};
47
94c00c71 48# A lacuna node is also a meta node.
49before is_lacuna => sub {
50 my( $self, $arg ) = @_;
51 if( $arg ) {
52 $self->is_meta( 1 );
53 }
54};
55
d047cd52 56# Initialize the identity pool.
57sub BUILD {
1ca1163d 58 my( $self, $args ) = @_;
59 my $pool = weak_set( $self );
60 $self->same_as( $pool );
d047cd52 61}
784877d9 62
e2902068 63sub text {
64 # Wrapper function around 'label' attribute.
65 my $self = shift;
66 if( @_ ) {
eca16057 67 if( defined $_[0] ) {
68 $self->set_attribute( 'label', $_[0] );
69 } else {
70 $self->del_attribute( 'label' );
71 }
e2902068 72 }
9463b0bf 73 return $self->label;
e2902068 74}
75
910a0a6d 76sub witnessed_by {
1ca1163d 77 my( $self, $sigil, $backup ) = @_;
78 my @wits = $self->witnesses;
79 return 1 if grep { $_ eq $sigil } @wits;
80 if( $backup ) {
81 return 1 if grep { $_ eq $backup } @wits;
82 }
83 return 0;
910a0a6d 84}
85
86sub witnesses {
1ca1163d 87 my( $self ) = @_;
88 my @paths = grep { $_->get_attribute( 'class' ) eq 'path' } $self->outgoing;
89 push( @paths, grep { $_->get_attribute( 'class' ) eq 'path' } $self->incoming );
90 my %wits;
91 foreach my $p ( @paths ) {
92 if( $p->has_hidden_witnesses ) {
93 foreach ( @{$p->hidden_witnesses} ) {
94 $wits{$_} = 1;
95 }
96 } else {
97 $wits{$p->label} = 1;
98 }
99 }
100 return keys %wits;
910a0a6d 101}
102
784877d9 103sub merge_from {
1ca1163d 104 my( $self, $merged_node ) = @_;
105 if( $self eq $merged_node ) {
106 warn "Trying to merge a reading into itself";
107 return;
108 }
109 # Adopt the identity pool of the other node.
110 my @now_identical = grep { $_ ne $merged_node && $_ ne $self }
111 $merged_node->same_as->members;
112 if( @now_identical ) {
113 _merge_array_pool( $self->same_as, @now_identical );
114 }
784877d9 115}
116
3265b0ce 117## Dealing with transposed readings. These methods are only really
118## applicable if we have a linear collation graph.
119
784877d9 120sub set_identical {
1ca1163d 121 my( $self, $other_node ) = @_;
122 if( $self eq $other_node ) {
123 warn "Trying to set a reading identical to itself";
124 return;
125 }
126 # We are no longer primary; turn that off first.
127 my @new_members = grep { $_ ne $other_node } $self->same_as->members;
128 _merge_array_pool( $other_node->same_as, @new_members );
129}
784877d9 130
de51424a 131sub identical_readings {
1ca1163d 132 my $self = shift;
133 my @same = grep { $_ ne $self } $self->same_as->members;
134 return @same;
de51424a 135}
136
1ca1163d 137## Helper function -
784877d9 138sub _merge_array_pool {
1ca1163d 139 my( $pool, @new_members ) = @_;
140 $pool->insert( @new_members );
141 foreach my $n ( @new_members ) {
142 $n->is_primary( 0 );
143 $n->same_as( $pool );
144 }
784877d9 145}
d047cd52 146
8e1394aa 147sub has_primary {
1ca1163d 148 my $self = shift;
149 return !$self->is_primary;
8e1394aa 150}
151
152sub primary {
1ca1163d 153 my $self = shift;
154 my @p = grep { $_->is_primary } $self->same_as->members;
155 warn "Identity pool for " . $self->name . " has more than one primary"
156 if @p > 1;
157 warn "Identity pool for " . $self->name . " has no primary" unless @p;
158 return shift( @p );
8e1394aa 159}
160
94c00c71 161# Looks from the outside like an accessor for a Boolean, but really
162# sets the node's class. Should apply to start, end, and lacunae.
163
164sub is_meta {
1ca1163d 165 my $self = shift;
166 my $arg = shift;
167 if( defined $arg && $arg ) {
168 $self->set_attribute( 'class', 'meta' );
169 } elsif ( defined $arg ) {
170 $self->del_attribute( 'class' );
171 }
172 return $self->sub_class eq 'meta';
94c00c71 173}
174
4cdd82f1 175# Returns all readings that adjoin this one on any path.
176sub neighbor_readings {
1ca1163d 177 my( $self, $direction ) = @_;
178 $direction = 'both' unless $direction;
179 my @paths = grep { $_->isa( 'Text::Tradition::Collation::Path' ) } $self->edges;
180 my %connected;
181 foreach my $p ( @paths ) {
182 if( $p->to eq $self ) {
183 next if $direction eq 'forward';
184 $connected{$p->from->name} = $p->from;
185 } else { # $p->from eq $self
186 next if $direction =~ /^back/;
187 $connected{$p->to->name} = $p->to;
188 }
4cdd82f1 189 }
1ca1163d 190 return values( %connected );
4cdd82f1 191}
192
910a0a6d 193# Returns all readings related to the one we've got.
194sub related_readings {
1ca1163d 195 my( $self, $colocated, $queried ) = @_;
196 $queried = { $self->name => 1 } unless $queried;
197 my @related;
198 # Get the nodes directly related to this one
199 foreach my $e ( $self->edges ) {
200 next unless $e->isa( 'Text::Tradition::Collation::Relationship' );
201 next if $colocated && $e->type eq 'repetition';
202 my $n = $e->from eq $self ? $e->to : $e->from;
203 next if $queried->{$n->name};
204 push( @related, $n );
205 }
206 # Now query those nodes for their relations, recursively
207 map { $queried->{$_->name} = 1 } @related;
208 my @also_related;
209 foreach ( @related ) {
210 push( @also_related, $_->related_readings( $colocated, $queried ) );
211 }
212 push( @related, @also_related );
213 return @related;
4cdd82f1 214}
215
3265b0ce 216## Keep track of which readings are unchanged across witnesses.
4a8828f0 217sub is_common {
1ca1163d 218 my( $self ) = shift;
219 return $self->get_attribute( 'class' ) eq 'common';
4a8828f0 220}
221
94c00c71 222## TODO Rationalize make_common, is_meta, etc.
4a8828f0 223sub make_common {
1ca1163d 224 my( $self ) = shift;
225 $self->set_attribute( 'class', 'common' );
4a8828f0 226}
227
228sub make_variant {
1ca1163d 229 my( $self ) = shift;
230 $self->set_attribute( 'class', 'variant' );
4a8828f0 231}
232
d047cd52 233no Moose;
234__PACKAGE__->meta->make_immutable;
235
2361;
237
238######################################################
239## copied from Graph::Easy::Parser docs
240######################################################
241# when overriding nodes, we also need ::Anon
242
243package Text::Tradition::Collation::Reading::Anon;
021bdbac 244use Moose;
245use MooseX::NonMoose;
246extends 'Text::Tradition::Collation::Reading';
247extends 'Graph::Easy::Node::Anon';
248no Moose;
249__PACKAGE__->meta->make_immutable;
d047cd52 250
021bdbac 2511;
252# use base qw/Text::Tradition::Collation::Reading/;
253# use base qw/Graph::Easy::Node::Anon/;
d047cd52 254
255######################################################
256# and :::Empty
257
258package Text::Tradition::Collation::Reading::Empty;
021bdbac 259use Moose;
260use MooseX::NonMoose;
261extends 'Graph::Easy::Node::Empty';
262no Moose;
263__PACKAGE__->meta->make_immutable;
d047cd52 264
021bdbac 2651;
266# use base qw/Text::Tradition::Collation::Reading/;
d047cd52 267
268######################################################