diff variant_effect_predictor/Bio/Tools/StateMachine/AbstractStateMachine.pm @ 0:1f6dce3d34e0

Uploaded
author mahtabm
date Thu, 11 Apr 2013 02:01:53 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/variant_effect_predictor/Bio/Tools/StateMachine/AbstractStateMachine.pm	Thu Apr 11 02:01:53 2013 -0400
@@ -0,0 +1,616 @@
+#-----------------------------------------------------------------
+# $Id: AbstractStateMachine.pm,v 1.9 2002/10/22 07:38:49 lapp Exp $
+#
+# BioPerl module Bio::Tools::StateMachine::AbstractStateMachine
+#
+# Cared for by Steve Chervitz <sac@bioperl.org>
+#
+# You may distribute this module under the same terms as perl itself
+#-----------------------------------------------------------------
+
+=head1 NAME
+
+Bio::Tools::StateMachine::AbstractStateMachine - Abstract state machine object
+
+=head1 SYNOPSIS
+
+Here is a portion of an implementation. For the full example, see
+examples/state-machine.pl in the Bioperl distribution.
+
+    package SimpleStateMachine;
+
+    use Bio::Root::Root;
+    use Bio::Tools::StateMachine::AbstractStateMachine qw($INITIAL_STATE 
+    							  $FINAL_STATE);
+    use vars qw( @ISA );
+
+    @ISA = qw( Bio::Root::Root
+    	   Bio::Tools::StateMachine::AbstractStateMachine );
+
+    my @state_transitions = (  [ $INITIAL_STATE, 'State1'],
+    			       [ 'State1', 'State2' ],
+    			       [ 'State2', $FINAL_STATE]
+    				);
+    sub new {
+       my($caller,@args) = @_;
+       my $self = $caller->SUPER::new( @args);
+       $self->_init_state_machine( -transition_table => \@state_transitions );
+       return $self;
+    }
+
+
+
+=head1 DESCRIPTION
+
+B<AbstractStateMachine> provides a generic framework for representing a
+state machine. This is not an event-based framework where you register
+handlers to be called when certain events occur. Instead, it provides
+a set of methods that define the basic logic of an object that has
+state behavior, that logic being:
+
+=over 4
+
+=item 1. Check for whether or not a new state has occurred in the external world.
+
+=item 2. If so, change the state of the machine to the new state.
+
+=item 3. Otherwise, keep checking current conditions for a new state.
+
+=item 4. Stop checking for new states if we reach the final state, or if an error occurs.
+
+=back
+
+A B<state> is just a string representing the name of the state.  A
+state machine is initialized with a B<state transition table>
+consisting of a set of allowable transitions, where each B<transition>
+is a two-element array in which the first element is the B<from
+state>, and the second element is the B<to state>.  This table permits
+the AbstractStateMachine to determine if a requested transition is
+valid.
+
+This module is flexible enough to represent both deterministic and
+non-deterministic finite automata (DFAs and NFAs), but it is fairly
+new and should be considered experimental.
+
+The key methods in AbstractStateMachine that define this logic of
+operation are:
+
+=over 4
+
+=item check_for_new_state().
+
+Does whatever checking is necessary to determine if a state transition
+should occur (for example, read a line of input from STDIN). If a
+transition should occur, a string is returned containing the name of
+the new state. Otherwise, it returns C<undef>.
+
+This method B<must be implemented> as AbstractStateMachine does not
+define it (and in fact will throw a NotImplemented exception if you
+fail to implement it).
+
+=item change_state( C<new_state> )
+
+Causes the machine to change its state to the state specified in the
+argument. change_state() allows you to mapping a state transition to a
+particular handler method that does whatever processing is needed to
+deal with the state transition.
+
+=item run()
+
+This method keeps calling check_for_new_state() and if that method
+returns a defined value (the name of the state to change to), it then
+calls change_state( $state ), where $state is the value returned by
+check_for_new_state().
+
+Before calling check_for_new_state(), the run() method checks the
+current state of the machine and exits the loop if the current state
+ever becomes $PAUSE_STATE, $ERROR_STATE, or $FINAL_STATE.
+
+=item append_input_cache( C<data> )
+
+Adds data to a buffer for processing at the next state
+transition. check_for_new_state() should call
+append_input_cache() passing it any data it receives while checking
+for a new state that should be processed later.
+
+=item get_input_cache()
+
+Retrieves the data stored by calling
+append_input_cache(). change_state() should call get_input_cache() to
+obtain the data to be processed for the current transition.
+
+=back
+
+This object defines an abstract class, meaning that some but not all methods 
+have been implemented. Subclasses must define the methods not implemented here.
+These include at a minimum:
+
+=over 4
+
+=item check_for_new_state()
+
+=item change_state()
+
+A default simple implementation of change_state() is provided, but
+subclasses of AbstractStateMachine most likely will want to override
+this method to do something useful according to the particular state
+change.
+
+=back
+
+If your state machine needs to cache input while processing, you will
+also need to provide implementations of these methods (which are no-op
+in AbstractStateMachine):
+
+=over 3
+
+=item append_input_cache
+
+=item get_input_cache
+
+=item clear_input_cache
+
+=back
+
+There are some other nuances provided by AbstractStateMachine, such as
+the ability to pause() and resume() the running of the machine.
+
+
+=head1 EXAMPLES
+
+To get a feel for how to use this, have look at scripts in the
+examples/state-machine directory of the Bioperl distribution. Also
+have a look at Bio::Tools::StateMachine::IOStateMachine which provides
+a Bio::Root::IO-based implementation of
+AbstractStateMachine. Bio::SearchIO::psiblast subclasses
+IOStateMachine.
+
+
+=head1 FEEDBACK
+
+=head2 Mailing Lists 
+
+User feedback is an integral part of the evolution of this and other
+Bioperl modules.  Send your comments and suggestions preferably to one
+of the Bioperl mailing lists.  Your participation is much appreciated.
+
+    bioperl-l@bioperl.org              - General discussion
+    http://bio.perl.org/MailList.html  - About the mailing lists
+
+=head2 Reporting Bugs
+
+Report bugs to the Bioperl bug tracking system to help us keep track
+the bugs and their resolution. Bug reports can be submitted via email
+or the web:
+
+    bioperl-bugs@bio.perl.org                   
+    http://bugzilla.bioperl.org/           
+
+=head1 AUTHOR 
+
+Steve Chervitz, E<lt>sac@bioperl.orgE<gt>
+
+See the L<FEEDBACK | FEEDBACK> section for where to send bug reports and comments.
+
+=head1 ACKNOWLEDGEMENTS
+
+I would like to acknowledge my colleagues at Affymetrix for useful
+feedback.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2001 Steve Chervitz. All Rights Reserved.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+=head1 DISCLAIMER
+
+This software is provided "as is" without warranty of any kind.
+
+=head1 APPENDIX
+
+The rest of the documentation details each of the object methods.
+Internal methods are usually preceded with a _
+
+=cut
+
+
+# Let the code begin...
+
+package Bio::Tools::StateMachine::AbstractStateMachine;
+
+use strict;
+use Bio::Root::RootI;
+use Exporter ();
+
+use vars qw( @ISA @EXPORT_OK $INITIAL_STATE $FINAL_STATE $PAUSE_STATE $ERROR_STATE );
+@ISA = qw( Bio::Root::RootI  Exporter );
+@EXPORT_OK = qw( $INITIAL_STATE $FINAL_STATE $PAUSE_STATE $ERROR_STATE );
+
+@Bio::Tools::StateMachine::StateException::ISA = qw( Bio::Root::Exception );
+
+$INITIAL_STATE = 'Initial';
+$FINAL_STATE = 'Final';
+$PAUSE_STATE = 'Pause';
+$ERROR_STATE = 'Error';
+
+sub _init_state_machine {
+    my  ($self, @args ) = @_;
+    my ($transition_table) = $self->_rearrange( [qw(TRANSITION_TABLE)], @args);
+
+    $self->verbose and print STDERR "Initializing State Machine...\n";
+
+    if($transition_table) {
+        $self->_set_transition_table( $transition_table );
+    }
+
+    $self->add_transition( $INITIAL_STATE, $FINAL_STATE );
+    $self->_set_current_state( $INITIAL_STATE );
+}
+
+sub reset {
+    my $self = shift;
+    $self->verbose and print STDERR "Resetting state machine\n";
+    $self->_set_current_state( $INITIAL_STATE );
+}
+
+sub _set_current_state {
+    my ($self, $state) = @_;
+    if( defined $state) {
+	$self->verbose and print STDERR "  setting current state to $state\n";
+	$self->{'_current_state'} = $state;
+    }
+}
+
+sub current_state { shift->{'_current_state'} }
+
+sub initial_state { $INITIAL_STATE }
+
+sub final_state { $FINAL_STATE }
+
+sub pause_state { $PAUSE_STATE }
+
+sub error_state { $ERROR_STATE }
+
+sub resume_state {
+    my ($self, $state) = @_;
+    if( $state ) {
+      $self->{'_resume_state'} = $state;
+    }
+    $self->{'_resume_state'};
+}
+
+sub _clear_resume_state {
+    my $self = shift;
+    undef $self->{'_resume_state'};
+}
+
+=head2 running
+
+The machine is either running or not running. 
+Once the machine has stopped running, it cannot be re-started.
+Use pause() to temporarily halt a machine without exiting the run state.
+
+=cut 
+
+sub running { shift->{'_running'} }
+
+sub _set_running {
+    my $self = shift;
+    $self->{'_running'} = shift;
+}
+
+sub run {
+    my ($self, @args) = @_;
+
+    my $verbose = $self->verbose;
+    my $curr_state = $self->current_state;
+    $self->_set_running( 1 );
+
+    while( not ($curr_state eq $PAUSE_STATE ||
+                $curr_state eq $ERROR_STATE ||
+                $curr_state eq $FINAL_STATE )) {
+
+	$verbose and print STDERR "Current state (run): ${\$self->current_state}\n";
+
+        if( my $state = $self->check_for_new_state()) {
+            $self->change_state( $state );
+        }
+
+        $curr_state = $self->current_state;
+    }
+
+    # Handle EOF situations
+    if( not ($curr_state eq $PAUSE_STATE ||
+             $curr_state eq $FINAL_STATE )) {
+
+        $self->change_state( $FINAL_STATE );
+	$self->_set_running( 0 );
+    }
+
+    $verbose and print STDERR "StateMachine Run complete ($curr_state).\n";
+}
+
+# The pause() and resume() methods don't go through change_state()
+sub pause {
+    my ($self) = @_;
+#    print "PAUSING...\n";
+    $self->resume_state( $self->current_state );
+    $self->_set_current_state( $PAUSE_STATE );
+#    print "After pause(): Current state: ${\$self->current_state}\n";
+}
+
+sub paused {
+    my ($self) = @_;
+    return $self->current_state eq $PAUSE_STATE;
+}
+
+sub throw{
+   my ($self,@args) = @_;
+   $self->_set_current_state( $ERROR_STATE );
+   $self->_set_running( 0 );
+   $self->SUPER::throw( @args );
+}
+
+sub error {
+    my ($self, $err) = @_;
+    return $self->current_state eq $ERROR_STATE;
+}
+
+sub resume {
+    my ($self) = @_;
+
+    # Don't resume if we're done.
+    return if $self->current_state eq $FINAL_STATE;
+
+#    print "RESUMING...\n";
+    $self->_set_current_state( $self->resume_state );
+    $self->_clear_resume_state;
+    $self->run();
+}
+
+=head2 transition_table
+
+ Arg      : n/a
+ Returns  : An array of array references to two-element arrays.
+            Each array ref defines a single transition where
+            the first element is the name of the "from" state and
+            the second element is the name of the "to" state.
+
+ Example  : $sm->transition_table( [ $INITIAL_STATE, 'State1'],
+				   [ 'State1', 'State2' ],
+				   [ 'State2', 'State3' ],
+				   [ 'State3', $FINAL_STATE]
+				 );
+
+=cut
+
+sub transition_table {
+    my ($self) = @_;
+
+    return @{$self->{'_transition_table'}};
+}
+
+sub _set_transition_table {
+    my ($self, $table_ref) = @_;
+
+    my $verbose = $self->verbose;
+    $verbose and print STDERR "Setting state transition table:\n";
+
+    if( not ref($table_ref) eq 'ARRAY') {
+	$self->throw( -class => 'Bio::Root::BadParameter',
+                      -text => "Can't set state transition table: Arg wasn't an array reference."
+                    );
+    }
+
+    foreach my $t (@$table_ref) {
+        if( ref($t) and scalar(@$t) == 2 ) {
+            push @{$self->{'_transition_table'}->{$t->[0]}}, $t->[1];
+            $verbose and print STDERR "  adding: $t->[0] -> $t->[1]\n";
+        }
+        else {
+            $self->throw( -class => 'Bio::Root::BadParameter',
+                          -text => "Can't add state transition from table: Not a 2-element array reference ($t)"
+                        );
+        }
+    }
+}
+
+=head2 add_transition
+
+ Arg      : Two string arguments where:
+            First string = name of the "from" state.
+            Second string = name of the "to" state.
+ Throws   : A Bio::Root::BadParameter exception if two arguments
+            are not provided.
+
+=cut
+
+sub add_transition {
+    my ($self, $from, $to) = @_;
+
+    if( defined($from) and defined($to) ) {
+	push @{$self->{'_transition_table'}->{$from}}, $to;
+    }
+    else {
+	$self->throw( -class => 'Bio::Root::BadParameter',
+                      -text => "Can't add state transition: Insufficient arguments."
+                    );
+    }
+}
+
+
+=head2 change_state
+
+ Purpose  : To cause the machine to change its state.
+ Argument : A String containing the name of the the new state.
+ Returns  : n/a
+ Throws   : A Bio::Tools::StateMachine::StateException exception if the
+            state transition cannot be carried out.
+
+This is a default implementation that simply validates the state change
+(by calling  validate_transition) and then calls finalize_state_change()
+if the transition is valid.
+
+Subclasses of AbstractStateMachine most likely will want to override this 
+method to do something useful according to the particular state change.
+
+=cut
+
+sub change_state {
+    my ($self, $new_state) = @_;
+
+    $self->verbose and print STDERR "  changing state to $new_state\n";
+
+    if ( $self->validate_transition( $self->current_state, $new_state, 1 ) ) {
+      $self->finalize_state_change( $new_state, 1 );
+    }
+
+}
+
+
+=head2 get_transitions_from
+
+ Purpose  : Returns a list array references that have the indicated state
+            in their 'from' slot.
+
+=cut
+
+sub get_transitions_from {
+    my ($self, $state) = @_;
+
+    my @trans = ();
+    if( ref $self->{'_transition_table'}->{$state}) {
+        @trans = @{$self->{'_transition_table'}->{$state}};
+    }
+
+    return @trans;
+}
+
+=head2 validate_transition
+
+ Purpose  : Determines if the desired state change is defined within 
+            the set of registered transitions for this StateMachine.
+ Arg      : Two required arguments:
+            [0] string defining the name of the "from" state (case sensitive)
+            [1] string defining the name of the "to" state (case sensitive)
+ Returns  : True if the transition is valid.
+            If not valid, throws an exception.
+ Throws   : A Bio::Tools::StateMachine::StateException if the desired 
+            transition does not exist with the registered transitions
+            for this machine.
+ Throws   : A Bio::Root::BadParameter if insufficient arguments are given.
+
+=cut
+
+sub validate_transition {
+    my ($self, $from_state, $to_state ) = @_;
+
+    #print STDERR "  validating transition $from_state -> $to_state\n";
+
+    if( not( defined($from_state) and defined($to_state))) {
+        $self->throw( -class => 'Bio::Root::BadParameter',
+                      -text => "Can't validate state transition: Insufficient arguments.");
+    }
+
+    my $is_valid = 0;
+
+    foreach my $t ( $self->get_transitions_from( $from_state ) ) {
+        if( $t eq $to_state ) {
+#        if( $t->[1] eq $to_state ) {
+            $is_valid = 1;
+            last;
+        }
+    }
+
+    if( not $is_valid ) {
+        $self->throw( -class => 'Bio::Tools::StateMachine::StateException',
+                      -text => "The desired state change is not valid for this machine: $from_state -> $to_state");
+    }
+
+    #print STDERR "  valid!\n";
+
+    return $to_state;
+}
+
+=head2 check_for_new_state
+
+ Purpose : To do whatever checking is necessary to determine if 
+            a state transition should occur. 
+ Argument : Any necessary data required to determine if the state 
+            machine should change to a new state.
+ Returns  : A string containing the name of the new state if the 
+            state machine should change to a new state. 
+            Otherwise returns undef.
+
+This is a virtual method and must be implemented by a subclass to do 
+whatever checking is necessary to determine if a state transition should occur.
+If not implemented, calling this method will result in a 
+Bio::Root::NotImplemented exception.
+
+=cut
+
+sub check_for_new_state {
+    my ($self, $data) = @_;
+    $self->throw_not_implemented;
+}
+
+sub append_input_cache {
+    my ($self, $data) = @_;
+}
+
+sub get_input_cache {
+    my $self = shift;
+}
+
+sub clear_input_cache {
+    my $self = shift;
+}
+
+sub state_change_cache {
+    my ($self, $data) = @_;
+    if( defined $data ) {
+        $self->{'_state_change_cache'} = $data;
+    }
+    return $self->{'_state_change_cache'};
+}
+
+sub clear_state_change_cache {
+    my ($self, $data) = @_;
+    $self->{'_state_change_cache'} = undef;
+}
+
+
+=head2 finalize_state_change
+
+ Purpose  : Performs routine operations to finish changing state.
+            This method should be called at the end of change_state().
+ Usage    : finalize_state_change( $new_state, $clear_input_cache );
+ Argument : $new_state = the name of the state to change to.
+            $clear_input_cache = boolean whether or not to zap whatever 
+                                 was in the input cache. Depends on 
+                                 the logic of your state machine.
+
+=cut
+
+sub finalize_state_change {
+    my ($self, $to_state, $clear_input_cache ) = @_;
+
+    if( $self->paused ) {
+        $self->resume_state( $to_state );
+    }
+    else {
+        $self->_set_current_state( $to_state );
+    }
+    $self->clear_input_cache() if $clear_input_cache;
+    $self->append_input_cache( $self->state_change_cache );
+    $self->clear_state_change_cache();
+}
+
+
+1;
+
+