/* AminoAcidSequence.java
 *
 * created: Sat Dec 19 1998
 *
 * This file is part of Artemis
 *
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/diana/sequence/AminoAcidSequence.java,v 1.19 2000/09/26 15:48:27 kmr Exp $
 */

package diana.sequence;

import diana.Options;

import uk.ac.sanger.pathogens.*;

/**
 *  Objects of this class represent a string of amino acids.
 *
 *  @author Kim Rutherford
 *  @version $Id: AminoAcidSequence.java,v 1.19 2000/09/26 15:48:27 kmr Exp $
 **/

public class AminoAcidSequence {
  /**
   *  Create a new AminoAcidSequence object from a string containing single
   *  character amino acids symbols.
   **/
  public AminoAcidSequence (String amino_acid_string) {
    this.amino_acid_string = amino_acid_string.toLowerCase ();
  }

  /**
   *  Translate a sequence of bases into the corresponding single letter amino
   *  acid codes.
   *  @param bases The bases to translated.  If the string length is not a
   *    multiple of three the last codon is incomplete and will not be
   *    translated.
   *  @return The translated sequence in one letter abbreviated form.
   **/
  public static AminoAcidSequence getTranslation (String bases) {
    final StringBuffer aa_buffer = new StringBuffer ();

    final int number_of_codons = bases.length () / 3;

    for (int i = 0 ; i < number_of_codons * 3 ; i += 3) {
      aa_buffer.append (getCodonTranslation (bases.charAt (i),
                                             bases.charAt (i+1),
                                             bases.charAt (i+2)));
    }

    return new AminoAcidSequence (aa_buffer.toString ());
  }

  /**
   *  Translate a single codon into the corresponding single letter amino acid
   *  code.
   *  @param codon_string A three character lowercase String containing the
   *    bases to translate.
   *  @return The translated sequence in one letter abbreviated form.  The
   *    return value will be '.' if the codon_string isn't a codon (eg. it
   *    contains more or less than three letters or the letters aren't from
   *    "CTAG")
   **/
  public static char getCodonTranslation (String codon_string) {
    final int symbol_index = codon_conversion_string.indexOf (codon_string);
    if (symbol_index == -1) {
      // this is an unusual case so this code won't slow things down too much
      if (codon_string.indexOf ('u') != -1) {
        // try to translate it as a RNA string 
        final int rna_symbol_index =
          codon_conversion_string.indexOf (codon_string);
        if (rna_symbol_index == -1) {
          return '.';
        } else {
          return amino_acid_symbol_array.charAt (rna_symbol_index);
        }
      }
      return '.';
    } else {
      return amino_acid_symbol_array.charAt (symbol_index);
    }
  }

  /**
   *  This table is used for fast lookup of codon translations by
   *  getCodonTranslation ().  There is one entry for each codon and the
   *  entries are in this order: TTT, TTC, TTA, TTG, TCT, TCC, ...
   **/
  final public static char [] codon_translation_array = {
    'f', 'f', 'l', 'l',
    's', 's', 's', 's',
    'y', 'y', '#', '+',
    'c', 'c', '*', 'w',

    'l', 'l', 'l', 'l',
    'p', 'p', 'p', 'p',
    'h', 'h', 'q', 'q',
    'r', 'r', 'r', 'r',

    'i', 'i', 'i', 'm',
    't', 't', 't', 't',
    'n', 'n', 'k', 'k',
    's', 's', 'r', 'r',

    'v', 'v', 'v', 'v',
    'a', 'a', 'a', 'a',
    'd', 'd', 'e', 'e',
    'g', 'g', 'g', 'g',
  };

  /**
   *  Translate a single codon into the corresponding single letter amino acid
   *  code.
   *  @param first_letter The first base of the codon.
   *  @param second_letter The second base of the codon.
   *  @param third_letter The third base of the codon.
   *  @return The translated sequence in one letter abbreviated form.  The
   *    return value will be -1 if the letters do not form a codon.
   **/
  public static char getCodonTranslation (char first_letter,
                                          char second_letter,
                                          char third_letter) {
    final int first_index = Bases.getIndexOfBase (first_letter);

    if (first_index >= 4) {
      return '.';
    }

    final int second_index = Bases.getIndexOfBase (second_letter);

    if (second_index >= 4) {
      return '.';
    }

    final int third_index = Bases.getIndexOfBase (third_letter);

    if (third_index >= 4) {
      return '.';
    }

    final int codon_index = first_index * 16 + second_index * 4 + third_index;

    return codon_translation_array[codon_index];
  }

  /**
   *  Return the number of units in this amino acid sequence.
   **/
  public int length () {
    return amino_acid_string.length ();
  }

  /**
   *  Return the one letter codon code of the codon at the given index
   *  (counting from zero).
   **/
  public char elementAt (final int index) {
    return amino_acid_string.charAt (index);
  }

  /**
   *  Return the total molecular weight of the amino acids in this
   *  AminoAcidSequence..
   **/
  public float getMolecularWeight () {
    float return_weight = 0;

    for (int i = 0 ; i < amino_acid_string.length () ; ++i) {
      final char this_char = amino_acid_string.charAt (i);

      return_weight +=
        molecular_weights[getSymbolIndex (this_char)];
    }

    if (amino_acid_string.length () > 1) {
      // need to take off the weight of a water molecule for each peptide bond
      return return_weight -
        molecular_weight_of_water * (amino_acid_string.length () - 1);
    } else {
      return return_weight;
    }
  }

  /**
   *  Return a string representation of this object.  This string will contain
   *  the one character amino acid codes for each acid in sequence.
   **/
  public String toString () {
    return amino_acid_string;
  }


  /**
   *  Find the next occurrence of this seqeuence on either Strand of the given
   *  Bases object.  This method searches both strands simultaneously by
   *  searching the underlying Bases object directly.
   *  @param bases This holds the Strand objects to search.
   *  @param search_start_marker The match that will be returned will be
   *    after this base Marker position.  Position 1 on the reverse strand is
   *    considered to be after position 1 on the forward strand, forward
   *    position 2 is after reverse position 1, reverse position 2 is after
   *    forward position 2, etc.  This scheme allows the caller to iterate
   *    through all matches.
   *  @param search_end_position The search will not extend past this
   *    position.
   *  @param search_backwards If true the search will move from last base to
   *    first base, otherwise first to last.
   *  @return A MarkerRange covering the matching bases or null if there is no
   *    match in the given range.
   **/
  public MarkerRange findMatch (final Bases bases,
                                final Marker search_start_marker,
//                                final int search_end_position,
                                final boolean search_backwards) {
    final String bases_string = bases.toString ();

    // search the bases_string forward for the pattern_string and its
    // complement

    // the String index position in bases_string at which to start the search
    // for this pattern
    final int forward_search_start_index;

    // the String index position in bases_string at which to start the search
    // for the reverse complement of this position
    final int complement_search_start_index;

    if (search_backwards) {
      if (search_start_marker == null) {
        forward_search_start_index = bases.getLength () - 1;
        complement_search_start_index = bases.getLength () - 1;
      } else {
        complement_search_start_index =
          search_start_marker.getRawPosition () - 2;
        if (search_start_marker.getStrand ().isForwardStrand ()) {
          forward_search_start_index =
            search_start_marker.getRawPosition () - 2;
        } else {
          forward_search_start_index =
            search_start_marker.getRawPosition () - 1;
        }
      }
    } else {
      if (search_start_marker == null) {
        forward_search_start_index = 0;
        complement_search_start_index = 0;
      } else {
        forward_search_start_index = search_start_marker.getRawPosition ();
        if (search_start_marker.getStrand ().isForwardStrand ()) {
          complement_search_start_index =
            search_start_marker.getRawPosition () - 1;
        } else {
          complement_search_start_index =
            search_start_marker.getRawPosition ();
        }
      }
    }

    final int forward_search_result =
      searchFor (bases_string,
                 forward_search_start_index,
                 search_backwards);

    final int complement_search_result =
       reverseComplementSearchFor (bases_string,
                                   complement_search_start_index,
                                   search_backwards);

    final int match_first_base;
    final int match_last_base;

    final Strand match_strand;

    if (forward_search_result == -1) {
      if (complement_search_result == -1) {
        // no match
        return null;
      }
    }

    if (search_backwards) {
      // take the match that is closest to the end, or the complement match if
      // there is a tie
      if (complement_search_result != -1 &&
          (forward_search_result == -1 ||
           complement_search_result >= forward_search_result)) {
        match_first_base =
          bases.getComplementPosition (complement_search_result + 1);
        match_last_base = match_first_base - (length () * 3 - 1);
        match_strand = bases.getReverseStrand ();
      } else {
        match_first_base = forward_search_result + 1;
        match_last_base = match_first_base + length () * 3 - 1;
        match_strand = bases.getForwardStrand ();
      }
    } else {
      // take the match that is closest to base 1, or the forward match if
      // there is a tie
      if (forward_search_result != -1 &&
          (complement_search_result == -1 ||
           forward_search_result <= complement_search_result)) {
        match_first_base = forward_search_result + 1;
        match_last_base = match_first_base + length () * 3 - 1;
        match_strand = bases.getForwardStrand ();
      } else {
        match_first_base =
          bases.getComplementPosition (complement_search_result + 1);
        match_last_base = match_first_base - (length () * 3 - 1);
        match_strand = bases.getReverseStrand ();
      }
    }

    try {
      return new MarkerRange (match_strand,
                              match_first_base,
                              match_last_base);
    } catch (OutOfRangeException e) {
      throw new Error ("internal error - unexpected exception: " + e);
    }
  }

  /**
   *  Search for this AminoAcidSequence in the given String of bases.  The
   *  String is treated as a sequence of bases and this AminoAcidSequence is
   *  searched for in each of the three reading frames.
   *  @param bases_string Search this String for the amino acid sequence.
   *  @param start_index This is the index in bases_string where the search
   *    should start.
   *  @param search_backwards If true the search will move from last base to
   *    first base, otherwise first to last.
   *  @return The index of the match or -1 if there is no match.
   **/
  public int searchFor (final String bases_string,
                        final int start_index,
                        final boolean search_backwards) {
    if (search_backwards) {
      return searchBackwardFor (bases_string, start_index);
    } else {
      return searchForwardFor (bases_string, start_index);
    }
  }

  /**
   *  Search forward for this AminoAcidSequence in the given String of bases.
   *  The String is treated as a sequence of bases and this AminoAcidSequence
   *  is searched for in each of the three reading frames.
   *  @param bases_string Search this String for the amino acid sequence.
   *  @param start_index This is the index in bases_string where the search
   *    should start.
   *  @return The index of the match or -1 if there is no match.
   **/
  public int searchForwardFor (final String bases_string,
                               final int start_index) {
    final int pattern_base_length = length () * 3;

    for (int base_index = start_index ;
         base_index <= bases_string.length () - pattern_base_length ;
         ++base_index) {

      boolean matched = true;

      for (int offset = 0 ; offset < length () ; ++offset) {
        final char base1 = bases_string.charAt (base_index + offset * 3 + 0);
        final char base2 = bases_string.charAt (base_index + offset * 3 + 1);
        final char base3 = bases_string.charAt (base_index + offset * 3 + 2);

        if (getCodonTranslation (base1, base2, base3) !=
            amino_acid_string.charAt (offset)) {
          matched = false;
          break;
        }
      }

      if (matched) {
        return base_index;
      }
    }

    return -1;
  }

  /**
   *  Search backward for this AminoAcidSequence in the given String of bases.
   *  The String is treated as a sequence of bases and this AminoAcidSequence
   *  is searched for in each of the three reading frames.
   *  @param bases_string Search this String for the amino acid sequence.
   *  @param start_index This is the index in bases_string where the search
   *    should start.
   *  @return The index of the match or -1 if there is no match.
   **/
  public int searchBackwardFor (final String bases_string,
                                int start_index) {
    if (bases_string.length () - start_index < length () * 3) {
      start_index = bases_string.length () - length () * 3;
    }

    for (int base_index = start_index ;
         base_index >= 0 ;
         --base_index) {

      boolean matched = true;

      for (int offset = 0 ; offset < length () ; ++offset) {
        final char base1 = bases_string.charAt (base_index + offset * 3 + 0);
        final char base2 = bases_string.charAt (base_index + offset * 3 + 1);
        final char base3 = bases_string.charAt (base_index + offset * 3 + 2);

        if (getCodonTranslation (base1, base2, base3) !=
            amino_acid_string.charAt (offset)) {
          matched = false;
          break;
        }
      }

      if (matched) {
        return base_index;
      }
    }

    return -1;
  }

  /**
   *  Search for this AminoAcidSequence in the reverse complement of the given
   *  String of bases.  The String is treated as a sequence of bases and this
   *  AminoAcidSequence is searched for in each of the three reading frames.
   *  @param bases_string Search this String for the amino acid sequence.
   *  @param start_index This is the index in bases_string where the search
   *    should start.
   *  @param search_backwards If true the search will move from last base to
   *    first base, otherwise first to last.
   *  @return The index of the match or -1 if there is no match.
   **/
  public int reverseComplementSearchFor (final String bases_string,
                                         final int start_index,
                                         final boolean search_backwards) {
    if (search_backwards) {
      return reverseComplementSearchBackwardFor (bases_string, start_index);
    } else {
      return reverseComplementSearchForwardFor (bases_string, start_index);
    }
  }

  /**
   *  Search forward for this AminoAcidSequence in the reverse complement of
   *  the given String of bases.  The String is treated as a sequence of bases
   *  and this AminoAcidSequence is searched for in each of the three reading
   *  frames.
   *  @param bases_string Search this String for the amino acid sequence.
   *  @param start_index This is the index in bases_string where the search
   *    should start.
   *  @return The index of the match or -1 if there is no match.
   **/
  public int reverseComplementSearchForwardFor (final String bases_string,
                                                final int start_index) {
    final int pattern_base_length = length () * 3;

    for (int base_index = start_index ;
         base_index <= bases_string.length () - pattern_base_length ;
         ++base_index) {

      boolean matched = true;

      for (int offset = 0 ; offset < length () ; ++offset) {
        final char base1 =
          Bases.complement (bases_string.charAt (base_index + offset * 3 + 0));
        final char base2 =
          Bases.complement (bases_string.charAt (base_index + offset * 3 + 1));
        final char base3 =
          Bases.complement (bases_string.charAt (base_index + offset * 3 + 2));

        final char amino_acid_char =
          amino_acid_string.charAt (amino_acid_string.length () - offset - 1);

        if (getCodonTranslation (base3, base2, base1) != amino_acid_char) {
          matched = false;
          break;
        }
      }

      if (matched) {
        return base_index;
      }
    }

    return -1;
  }

  /**
   *  Search backward for this AminoAcidSequence in the reverse complement of
   *  the given String of bases.  The String is treated as a sequence of bases
   *  and this AminoAcidSequence is searched for in each of the three reading
   *  frames.
   *  @param bases_string Search this String for the amino acid sequence.
   *  @param start_index This is the index in bases_string where the search
   *    should start.
   *  @return The index of the match or -1 if there is no match.
   **/
  public int reverseComplementSearchBackwardFor (final String bases_string,
                                                 int start_index) {

    if (bases_string.length () - start_index < length () * 3) {
      start_index = bases_string.length () - length () * 3;
    }

    for (int base_index = start_index ;
         base_index >= 0 ;
         --base_index) {

      boolean matched = true;

      for (int offset = 0 ; offset < length () ; ++offset) {
        final char base1 =
          Bases.complement (bases_string.charAt (base_index + offset * 3 + 0));
        final char base2 =
          Bases.complement (bases_string.charAt (base_index + offset * 3 + 1));
        final char base3 =
          Bases.complement (bases_string.charAt (base_index + offset * 3 + 2));

        final char amino_acid_char =
          amino_acid_string.charAt (amino_acid_string.length () - offset - 1);

        if (getCodonTranslation (base3, base2, base1) != amino_acid_char) {
          matched = false;
          break;
        }
      }

      if (matched) {
        return base_index;
      }
    }

    return -1;
  }

  /**
   *  Return true if and only if the given amino acid symbol is the
   *  translation of a stop codon. ie #, * or +.
   **/
  public static boolean isStopCodon (final char amino_acid_char) {
    if (amino_acid_char == '#' ||
        amino_acid_char == '*' ||
        amino_acid_char == '+') {
      return true;
    } else {
      return false;
    }
  }

  /**
   *  Return true if and only if the given one letter code symbol is the a
   *  legal amino acid or stop symbol.
   **/
  public static boolean isLegalCodon (final char one_letter_code) {
    switch (one_letter_code) {
    case 'a': case 'r': case 'n': case 'd': case 'c': case 'q': case 'e':
    case 'g': case 'h': case 'i': case 'l': case 'k': case 'm': case 'f':
    case 'p': case 's': case 't': case 'w': case 'y': case 'v': case '*':
    case '#': case '+':
      return true;
    default:
      return false;
    }
  }

  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int POLAR_UNCHARGED_AA    = 0;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int POSITIVELY_CHARGED_AA = 1;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int NEGATIVELY_CHARGED_AA = 2;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int HYDROPHOBIC_AA        = 3;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int SPECIAL_AA            = 4;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int STOP_AA               = 5;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int UNKNOWN_AA            = 6;
  /**
   *  Used by getAminoAcidType ().
   **/
  public final static int ILLEGAL_AA            = 7;
  
  /**
   *  Returns one of POLAR_UNCHARGED_AA, POSITIVELY_CHARGED_AA,
   *  NEGATIVELY_CHARGED_AA, HYDROPHOBIC_AA, SPECIAL_AA or STOP_AA depending
   *  on the aa_char argument.
   **/
  public static int getAminoAcidType (final char aa_char) {
    switch (aa_char) {
    case 'S': case 'T': case 'N': case 'Q':
      return POLAR_UNCHARGED_AA;

    case 'K': case 'R': case 'H':
      return POSITIVELY_CHARGED_AA;

    case 'E': case 'D':
      return NEGATIVELY_CHARGED_AA;

    case 'A': case 'I': case 'L': case 'M': case 'F': case 'W': case 'V':
    case 'Y':
      return HYDROPHOBIC_AA;

    case 'C': case 'G': case 'P':
      return SPECIAL_AA;

    case '#': case '*': case '+':
      return STOP_AA;

    default:
      return ILLEGAL_AA;
    }
  }


  /**
   *  Return the one letter abbreviation for the given three letter amino acid
   *  name.
   *  @return A one letter code or -1 if three_letter_code can't be understood.
   **/
  public static char getOneLetterCode (final String three_letter_code) {
    final String real_code =
      three_letter_code.substring (0, 1).toUpperCase () +
      three_letter_code.substring (1).toLowerCase ();
    
    for (int i = 0 ; i < amino_acid_one_letter_names.length ; ++i) {
      if (real_code.equals (amino_acid_abbreviated_names[i])) {
        return amino_acid_one_letter_names[i];
      }
    }

    return (char) -1;
  }

  /**
   *  Return the three letter abbreviation for the given one letter amino acid
   *  code.
   **/
  public static String getThreeLetterAbbreviation (char one_letter_code) {
    for (int i = 0 ; i < amino_acid_one_letter_names.length ; ++i) {
      if (one_letter_code == amino_acid_one_letter_names[i]) {
        return amino_acid_abbreviated_names[i];
      }
    }

    throw new Error ("internal error - illegal one letter amino acid code");
  }

  /**
   *  Return the three letter abbreviation for the given index code.
   **/
  public static String getThreeLetterAbbreviation (final int index) {
    return amino_acid_abbreviated_names[index];
  }

  /**
   *  Return an integer from 0 to 22 representing the index of a codon
   *  symbol.
   **/
  public static int getSymbolIndex (char one_letter_code) {
    switch (one_letter_code) {
    case 'a': return 0;
    case 'r': return 1;
    case 'n': return 2;
    case 'd': return 3;
    case 'c': return 4;
    case 'q': return 5;
    case 'e': return 6;
    case 'g': return 7;
    case 'h': return 8;
    case 'i': return 9;
    case 'l': return 10;
    case 'k': return 11;
    case 'm': return 12;
    case 'f': return 13;
    case 'p': return 14;
    case 's': return 15;
    case 't': return 16;
    case 'w': return 17;
    case 'y': return 18;
    case 'v': return 19;
    case '*': return 20;
    case '#': return 21;
    case '+': return 22;
    case '.': return 23;
    case 'u': return 24;
    default:
      throw new Error ("Internal error - illegal one letter codon symbol: " +
                       one_letter_code);
    }
  }

  /**
   *  Given an index return a one letter codon symbol.  This method is the
   *  inverse of getSymbolIndex ().
   **/
  public static char getSymbolFromIndex (final int index) {
    return amino_acid_one_letter_names[index];
  }

  /**
   *  A String containg the amino acids symbols of this object.
   **/
  private String amino_acid_string = null;

  /**
   *  This array is arranged so that the index of the first occurrence of any
   *  three bases sequence gives the corresponding amino acid symbol when
   *  looked up in amino_acid_symbol_array.  For example the first occurrence
   *  of "gtc" in the array is index 60 and the symbol at index 60 of
   *  amino_acid_symbol_array is "v"
   *
   **/
  private final static String codon_conversion_string =
    "atgcgactgagaagccgtttgtggtaatccctatagggattaccacaaacggcttctcagtcgcat";

  /**
   *  Same as codon_conversion_string, with t changed to u.
   **/
  private final static String rna_codon_conversion_string =
    "augcgacugagaagccguuugugguaaucccuauagggauuaccacaaacggcuucucagucgcau";

  /**
   *  Used with codon_conversion_string translate a codon to amino acid symbol.
   **/
  private static String amino_acid_symbol_array =
    "mcardtl*ereksaprvflcvwgv#nispplyi+rggdilytphtqkntrgalfslsqsvsrah..";

  /**
   *  The three letter abbreviated names for the amino acids and stop codons.
   *  The names here correspond to the letter codes at the same indices in
   *  amino_acid_one_letter_names.
   **/
  private final static String [] amino_acid_abbreviated_names = {
    "Ala", "Arg", "Asn", "Asp", "Cys",
    "Gln", "Glu", "Gly", "His", "Ile",
    "Leu", "Lys", "Met", "Phe", "Pro",
    "Ser", "Thr", "Trp", "Tyr", "Val",
    "Opl", "Ocr", "Amb", "---", "Sel"
  };


  /**
   *  The one letter abbreviated names for the amino acids and stop codons.
   *  The names here correspond to the three letter codes at the same indices
   *  in amino_acid_abbreviated_names.
   **/
  private final static char [] amino_acid_one_letter_names = {
    'a', 'r', 'n', 'd', 'c',
    'q', 'e', 'g', 'h', 'i',
    'l', 'k', 'm', 'f', 'p',
    's', 't', 'w', 'y', 'v',
    '*', '#', '+', '.', 'u'
  };

  /**
   *  The molecular weights of the amino acids.  The values correspond to the
   *  three letter codes at the same indices in amino_acid_abbreviated_names.
   *  For example "Met" corresponds to a weight of 149.22
   **/
  private final static float [] molecular_weights = {
    89.09F,  174.21F, 132.12F, 133.10F, 121.15F,
    146.15F, 147.13F, 75.07F,  155.16F, 131.18F,
    131.18F, 146.19F, 149.22F, 165.19F, 115.13F,
    105.09F, 119.12F, 204.22F, 181.19F, 117.15F,
    0.0F,    0.0F,    0.0F,    0.0F,    334.1F
  };

  /**
   *  The average molecular weight of all amino acids.
   **/
  private final static float average_molecular_weight = 136.90F;

  /**
   *  The molecular weight of water.
   **/
  private final static float molecular_weight_of_water = 18.015F;

  /**
   *  The number of amino acid symbols, including stop codons and a symbol for
   *  unknown (---): 23.
   **/
  public final static int symbol_count = amino_acid_abbreviated_names.length;

  /**
   *  The number of amino acid symbols, not including stop codons: 20.
   **/
  public final static int amino_acid_symbol_count = 20;


  static {
    // if translation_table is in the options file use it to set
    // codon_translation_array
    final StringVector options_file_table =
      Options.getOptions ().getOptionValues ("translation_table");

    if (options_file_table != null) {
      if (options_file_table.size () == 64) {
        for (int i = 0 ; i < 64 ; ++i) {
          final char new_table_char =
            options_file_table.elementAt (i).charAt (0);

          if (isLegalCodon (new_table_char)) {
            codon_translation_array[i] = new_table_char;
          } else {
            codon_translation_array[i] = '.';
          }
        }
      }

      // now munge amino_acid_symbol_array so that it corresponds to
      // codon_translation_array

      final StringBuffer amino_acid_symbol_array_buffer = new StringBuffer (66);

      for (int i = 0 ; i < codon_conversion_string.length () - 2 ; ++i) {
        final char first_letter = codon_conversion_string.charAt (i);
        final char second_letter = codon_conversion_string.charAt (i + 1);
        final char third_letter = codon_conversion_string.charAt (i + 2);

        final char translation =
          getCodonTranslation (first_letter, second_letter, third_letter);

        amino_acid_symbol_array_buffer.append (translation);
      }

      amino_acid_symbol_array_buffer.append ("..");
      
      amino_acid_symbol_array = amino_acid_symbol_array_buffer.toString ();
    }
  }
}
