ensembl-hive  2.5
Collection.pm
Go to the documentation of this file.
1 =head1 LICENSE
2 
3 Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
4 Copyright [2016-2022] EMBL-European Bioinformatics Institute
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 =cut
19 
20 =pod
21 
22 =head1 NAME
23 
24 Bio::EnsEMBL::Hive::Utils::Collection - A collection object
25 
26 =cut
27 
28 package Bio::EnsEMBL::Hive::Utils::Collection;
29 
30 use strict;
31 use warnings;
32 
33 
34 sub new {
35  my $class = shift @_;
36 
37  my $self = bless {}, $class;
38 
39  $self->listref( shift @_ || [] );
40 
41  return $self;
42 }
43 
44 
45 sub listref {
46  my $self = shift @_;
47 
48  if(@_) {
49  $self->{'_listref'} = shift @_;
50  }
51  return $self->{'_listref'};
52 }
53 
54 
55 sub list {
56  my $self = shift @_;
57 
58  return @{ $self->listref };
59 }
60 
61 
62 sub present {
63  my $self = shift @_;
64  my $candidate = shift @_;
65 
66  foreach my $element (@{ $self->listref }) {
67  return 1 if($element eq $candidate);
68  }
69  return 0;
70 }
71 
72 
73 sub add {
74  my $self = shift @_;
75 
76  push @{ $self->listref }, @_;
77 }
78 
79 
80 sub add_once {
81  my $self = shift @_;
82  my $candidate = shift @_;
83 
84  unless( $self->present( $candidate ) ) {
85  $self->add( $candidate );
86  }
87 }
88 
89 
90 sub forget {
91  my $self = shift @_;
92  my $candidate = shift @_;
93 
94  my $listref = $self->listref;
95 
96  for(my $i=scalar(@$listref)-1;$i>=0;$i--) {
97  if($listref->[$i] eq $candidate) {
98  splice @$listref, $i, 1;
99  }
100  }
101 }
102 
103 
104 sub find_one_by {
105  my ($self, %method_to_filter_value) = @_;
106 
107  ELEMENT: foreach my $element (@{ $self->listref }) {
108  keys %method_to_filter_value; # sic! This is to "rewind" the each% operator to the beginning each time
109  while(my ($filter_name, $filter_value) = each %method_to_filter_value) {
110  my $actual_value = (ref($element) eq 'HASH') ? $element->{$filter_name} : $element->$filter_name();
111  next ELEMENT unless( defined($actual_value) # either both defined and equal or neither defined
112  ? defined($filter_value) && ( (ref($filter_value) eq 'CODE')
113  ? &$filter_value( $actual_value )
114  : ( $filter_value eq $actual_value )
115  )
116  : !defined($filter_value)
117  );
118  }
119  return $element;
120  }
121 
122  return undef; # have to be explicit here to avoid surprises
123 }
124 
125 
126 sub find_all_by {
127  my ($self, %method_to_filter_value) = @_;
128 
129  my @filtered_elements = ();
130 
131  ELEMENT: foreach my $element (@{ $self->listref }) {
132  keys %method_to_filter_value; # sic! This is to "rewind" the each% operator to the beginning each time
133  while(my ($filter_name, $filter_value) = each %method_to_filter_value) {
134  my $actual_value = (ref($element) eq 'HASH') ? $element->{$filter_name} : $element->$filter_name();
135  next ELEMENT unless( defined($actual_value) # either both defined and equal or neither defined
136  ? defined($filter_value) && ( (ref($filter_value) eq 'CODE')
137  ? &$filter_value( $actual_value )
138  : ( $filter_value eq $actual_value )
139  )
140  : !defined($filter_value)
141  );
142  }
143  push @filtered_elements, $element;
144  }
145 
146  return \@filtered_elements;
147 }
148 
149 
150 sub _find_all_by_subpattern { # subpatterns can be combined into full patterns using +-,
151  my ($self, $pattern) = @_;
152 
153  my $filtered_elements = [];
154  $pattern //= '';
155 
156  if( $pattern=~/^\d+$/ ) {
157 
158  $filtered_elements = $self->find_all_by( 'dbID', $pattern );
159 
160  } elsif( $pattern=~/^(\d+)\.\.(\d+)$/ ) {
161 
162  $filtered_elements = $self->find_all_by( 'dbID', sub { return $1<=$_[0] && $_[0]<=$2; } );
163 
164  } elsif( $pattern=~/^(\d+)\.\.$/ ) {
165 
166  $filtered_elements = $self->find_all_by( 'dbID', sub { return $1<=$_[0]; } );
167 
168  } elsif( $pattern=~/^\.\.(\d+)$/ ) {
169 
170  $filtered_elements = $self->find_all_by( 'dbID', sub { return $_[0]<=$1; } );
171 
172  } elsif( $pattern=~/^\w+$/) {
173 
174  $filtered_elements = $self->find_all_by( 'name', $pattern );
175 
176  } elsif( $pattern=~/^[\w\%]+$/) {
177 
178  $pattern=~s/\%/.*/g;
179  $filtered_elements = $self->find_all_by( 'name', sub { return $_[0]=~/^${pattern}$/; } );
180 
181  } elsif( $pattern=~/^(\w+)==(.*)$/) {
182 
183  $filtered_elements = $self->find_all_by( $1, $2 );
184 
185  } elsif( $pattern=~/^(\w+)!=(.*)$/) {
186 
187  $filtered_elements = $self->find_all_by( $1, sub { return $_[0] ne $2; } );
188 
189  } elsif( $pattern=~/^(\w+)<=(.*)$/) { # NB: the order is important - all digraphs should be parsed before their proper prefixes
190 
191  $filtered_elements = $self->find_all_by( $1, sub { return $_[0] <= $2; } );
192 
193  } elsif( $pattern=~/^(\w+)>=(.*)$/) {
194 
195  $filtered_elements = $self->find_all_by( $1, sub { return $_[0] >= $2; } );
196 
197  } elsif( $pattern=~/^(\w+)<(.*)$/) {
198 
199  $filtered_elements = $self->find_all_by( $1, sub { return $_[0] < $2; } );
200 
201  } elsif( $pattern=~/^(\w+)>(.*)$/) {
202 
203  $filtered_elements = $self->find_all_by( $1, sub { return $_[0] > $2; } );
204 
205  } else {
206  die "The pattern '$pattern' is not recognized\n";
207  }
208 
209  return $filtered_elements;
210 }
211 
212 
213 =head2 find_all_by_pattern
214 
215  Arg [1] : (optional) string $pattern
216  Example : my $first_fifteen_analyses_and_two_more = $collection->find_all_by_pattern( '1..15,analysis_X,21' );
217  Example : my $two_open_ranges = $collection->>find_all_by_pattern( '..7,10..' );
218  Example : my $double_exclusion = $collection->find_all_by_pattern( '1..15-3..5+4' );
219  Example : my $blast_related_with_exceptions = $collection->find_all_by_pattern( 'blast%-12-%funnel' );
220  Description: Filters an arrayref of non-repeating objects from the given collection by interpreting a pattern.
221  The pattern can contain individual analyses_ids, individual logic_names,
222  open and closed ranges of analysis_ids, wildcard patterns of logic_names,
223  merges (+ or ,) and exclusions (-) of the above subsets.
224  Exceptions : none
225  Caller : both beekeeper.pl (for scheduling) and runWorker.pl (for specialization)
226 
227 =cut
228 
229 sub find_all_by_pattern {
230  my ($self, $pattern) = @_;
231 
232  if( not defined($pattern) ) {
233 
234  return [ $self->list ];
235 
236  } else {
237 
238  # By using the grouping, we ask Perl to return the pattern and their delimiters
239  my @syll = split(/([+\-,])/, $pattern);
240 
241  my %uniq = map { ("$_" => $_) } @{ $self->_find_all_by_subpattern( shift @syll ) }; # initialize with the first syllable
242 
243  while(@syll) {
244  my $operation = shift @syll; # by construction this is one of [+-,]
245  my $subpattern = shift @syll; # can be an empty string
246 
247  foreach my $element (@{ $self->_find_all_by_subpattern( $subpattern ) }) {
248  if($operation eq '-') {
249  delete $uniq{ "$element" };
250  } else {
251  $uniq{ "$element" } = $element;
252  }
253  }
254  }
255 
256  return [ values %uniq ];
257  }
258 }
259 
260 
261 sub dark_collection { # contain another collection of objects marked for deletion
262  my $self = shift @_;
263 
264  if(@_) {
265  $self->{'_dark_collection'} = shift @_;
266  }
267  return $self->{'_dark_collection'};
268 }
269 
270 
271 sub forget_and_mark_for_deletion {
272  my $self = shift @_;
273  my $candidate = shift @_;
274 
275  $self->forget( $candidate );
276 
277  unless( $self->dark_collection ) {
278  $self->dark_collection( Bio::EnsEMBL::Hive::Utils::Collection->new );
279  }
280  $self->dark_collection->add( $candidate );
281 }
282 
283 
284 sub DESTROY {
285  my $self = shift @_;
286 
287  $self->listref( [] );
288  $self->dark_collection( undef );
289 }
290 
291 1;