Merge branch 'alpha/JAL-3362_Jalview_212_alpha' into alpha/merge_212_JalviewJS_2112
[jalview.git] / src / jalview / io / HMMFile.java
index 8870837..2fce4cc 100644 (file)
@@ -1,22 +1,20 @@
 package jalview.io;
 
+import jalview.api.AlignExportSettingsI;
+import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.HMMNode;
 import jalview.datamodel.HiddenMarkovModel;
 import jalview.datamodel.SequenceI;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Scanner;
 
 
 /**
- * Adds capability to read in and write out HMMER3 files. Currently only supports HMMER3/f.
+ * Adds capability to read in and write out HMMER3 files. .
  * 
  * 
  * @author TZVanaalten
@@ -25,51 +23,123 @@ import java.util.Scanner;
 public class HMMFile extends AlignFile
         implements AlignmentFileReaderI, AlignmentFileWriterI
 {
-  // HMM to store file data
-  private HiddenMarkovModel hmm = new HiddenMarkovModel();
+  private static final String TERMINATOR = "//";
 
-  // number of possible transitions
-  private final int NUMBER_OF_TRANSITIONS = 7;
+  /*
+   * keys to data in HMM file, used to store as properties of the HiddenMarkovModel
+   */
+  public static final String HMM = "HMM";
+
+  public static final String NAME = "NAME";
+
+  public static final String ACCESSION_NUMBER = "ACC";
+
+  public static final String DESCRIPTION = "DESC";
+
+  public static final String LENGTH = "LENG";
+
+  public static final String MAX_LENGTH = "MAXL";
+
+  public static final String ALPHABET = "ALPH";
+
+  public static final String DATE = "DATE";
+
+  public static final String COMMAND_LOG = "COM";
+
+  public static final String NUMBER_OF_SEQUENCES = "NSEQ";
+
+  public static final String EFF_NUMBER_OF_SEQUENCES = "EFFN";
+
+  public static final String CHECK_SUM = "CKSUM";
+
+  public static final String STATISTICS = "STATS";
+
+  public static final String COMPO = "COMPO";
+
+  public static final String GATHERING_THRESHOLD = "GA";
+
+  public static final String TRUSTED_CUTOFF = "TC";
+
+  public static final String NOISE_CUTOFF = "NC";
+
+  public static final String VITERBI = "VITERBI";
+
+  public static final String MSV = "MSV";
+
+  public static final String FORWARD = "FORWARD";
+
+  public static final String MAP = "MAP";
+
+  public static final String REFERENCE_ANNOTATION = "RF";
+
+  public static final String CONSENSUS_RESIDUE = "CONS";
+
+  public static final String CONSENSUS_STRUCTURE = "CS";
+
+  public static final String MASKED_VALUE = "MM";
+
+  private static final String ALPH_AMINO = "amino";
 
-  private final String NEW_LINE = "\n";
+  private static final String ALPH_DNA = "DNA";
 
-  //number of symbols in the alphabet used in the hidden Markov model
-  int numberOfSymbols;
+  private static final String ALPH_RNA = "RNA";
 
-  private final String SPACE = " ";
+  private static final String ALPHABET_AMINO = "ACDEFGHIKLMNPQRSTVWY";
 
-  private final String COMPO = "COMPO";
+  private static final String ALPHABET_DNA = "ACGT";
 
-  private final String EMPTY = "";
+  private static final String ALPHABET_RNA = "ACGU";
 
-  //This is a line that needs to be added to each HMMER� file. It is purely for readability.
-  private static final String TRANSITIONTYPELINE = "m->m     m->i     m->d     i->m     i->i     d->m     d->d";
+  private static final int NUMBER_OF_TRANSITIONS = 7;
+
+  private static final String SPACE = " ";
+
+  /*
+   * optional guide line added to an output HMMER file, purely for readability
+   */
+  private static final String TRANSITIONTYPELINE = "            m->m     m->i     m->d     i->m     i->i     d->m     d->d";
+
+  private static String NL = System.lineSeparator();
+
+  private HiddenMarkovModel hmm;
+
+  // number of symbols in the alphabet used in the hidden Markov model
+  private int numberOfSymbols;
+
+  /**
+   * Constructor that parses immediately
+   * 
+   * @param inFile
+   * @param type
+   * @throws IOException
+   */
+  public HMMFile(String inFile, DataSourceType type) throws IOException
+  {
+    super(inFile, type);
+  }
 
   /**
-   * Constructor for HMMFile, parses immediately
+   * Constructor that parses immediately
    * 
    * @param source
    * @throws IOException
    */
   public HMMFile(FileParse source) throws IOException
   {
-    super(false, source);
-    parse();
+    super(source);
   }
 
   /**
-   * Default constructor, do not use!
+   * Default constructor
    */
   public HMMFile()
   {
-
   }
 
   /**
-   * Constructor for HMMFile used for exporting.
+   * Constructor for HMMFile used for exporting
    * 
    * @param hmm
-   * @param exportImmediately
    */
   public HMMFile(HiddenMarkovModel markov)
   {
@@ -77,7 +147,7 @@ public class HMMFile extends AlignFile
   }
 
   /**
-   * Returns the HMM produced by reading in a HMMER3 file.
+   * Returns the HMM produced by parsing a HMMER3 file
    * 
    * @return
    */
@@ -87,17 +157,7 @@ public class HMMFile extends AlignFile
   }
 
   /**
-   * Sets the HMM used in this file.
-   * 
-   * @param model
-   */
-  public void setHMM(HiddenMarkovModel model)
-  {
-    this.hmm = model;
-  }
-
-  /**
-   * Gets the name of the hidden Markov model.
+   * Gets the name of the hidden Markov model
    * 
    * @return
    */
@@ -107,264 +167,253 @@ public class HMMFile extends AlignFile
   }
 
   /**
-   * Reads the data from HMM file into the HMM field on this object.
-   * 
-   * @throws IOException
+   * Reads the data from HMM file into the HMM model
    */
   @Override
-  public void parse() throws IOException
-  {
-    parseFileProperties(dataIn);
-    parseModel(dataIn);
-  }
-
-  /**
-   * Reads the data from HMM file into the HMM field on this object.
-   * 
-   * @throws IOException
-   */
-
-  public void parse(BufferedReader br) throws IOException
+  public void parse()
   {
-    parseFileProperties(br);
-    parseModel(br);
+    try
+    {
+      hmm = new HiddenMarkovModel();
+      parseHeaderLines(dataIn);
+      parseModel(dataIn);
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
   }
 
-
-
   /**
-   * Imports the file properties from a HMMER3 file.
+   * Reads the header properties from a HMMER3 file and saves them in the
+   * HiddeMarkovModel. This method exits after reading the next line after the
+   * HMM line.
    * 
    * @param input
-   *          The buffered reader used to read in the file.
    * @throws IOException
    */
-  void parseFileProperties(BufferedReader input) throws IOException
+  void parseHeaderLines(BufferedReader input) throws IOException
   {
-    boolean readingFile = true;
+    boolean readingHeaders = true;
     hmm.setFileHeader(input.readLine());
     String line = input.readLine();
-    while (readingFile)
+    while (readingHeaders && line != null)
     {
-      if (line != null)
+      Scanner parser = new Scanner(line);
+      String next = parser.next();
+      if (ALPHABET.equals(next))
       {
-        Scanner parser = new Scanner(line);
-        String next = parser.next();
-        if ("HMM".equals(next)) // indicates start of HMM data (end of file
-                              // properties)
-        {
-          readingFile = false;
-          hmm.fillSymbols(parser);
-          numberOfSymbols = hmm.getNumberOfSymbols();
-        }
-        else if ("STATS".equals(next))
-        {
-          parser.next();
-          String key;
-          String value;
-          key = parser.next();
-          value = parser.next() + SPACE + SPACE + parser.next();
-          hmm.addFileProperty(key, value);
-        }
-        else
+        String alphabetType = parser.next();
+        hmm.setProperty(ALPHABET, alphabetType);
+        String alphabet = ALPH_DNA.equalsIgnoreCase(alphabetType)
+                ? ALPHABET_DNA
+                : (ALPH_RNA.equalsIgnoreCase(alphabetType) ? ALPHABET_RNA
+                        : ALPHABET_AMINO);
+        numberOfSymbols = hmm.setAlphabet(alphabet);
+      }
+      else if (HMM.equals(next))
+      {
+        readingHeaders = false;
+        String symbols = line.substring(line.indexOf(HMM) + HMM.length());
+        numberOfSymbols = hmm.setAlphabet(symbols);
+      }
+      else if (STATISTICS.equals(next))
+      {
+        parser.next();
+        String key;
+        String value;
+        key = parser.next();
+        value = parser.next() + SPACE + SPACE + parser.next();
+        hmm.setProperty(key, value);
+      }
+      else
+      {
+        String key = next;
+        String value = parser.next();
+        while (parser.hasNext())
         {
-          String key = next;
-          String value = parser.next();
-          while (parser.hasNext())
-          {
-            value = value + SPACE + parser.next();
-          }
-          hmm.addFileProperty(key, value);
+          value = value + SPACE + parser.next();
         }
-        parser.close();
+        hmm.setProperty(key, value);
       }
+      parser.close();
       line = input.readLine();
-      if (line == null)
-      {
-        readingFile = false;
-      }
     }
-
   }
 
   /**
-   * Parses the model data from the HMMER3 file
+   * Parses the model data from the HMMER3 file. The input buffer should be
+   * positioned at the (optional) COMPO line if there is one, else at the insert
+   * emissions line for the BEGIN node of the model.
    * 
    * @param input
-   *          The buffered reader used to read the file.
    * @throws IOException
    */
   void parseModel(BufferedReader input) throws IOException
   {
+    /*
+     * specification says there must always be an HMM header (already read)
+     * and one more header (guide headings) which is skipped here
+     */
+    int nodeNo = 0;
     String line = input.readLine();
-    int node = 0;
-    while (!"//".equals(line))
+    List<HMMNode> nodes = new ArrayList<>();
+
+    while (line != null && !TERMINATOR.equals(line))
     {
-      hmm.getNodes().add(new HMMNode());
-      String next;
-      Scanner matchReader = new Scanner(line);
-      next = matchReader.next();
-      if (next.equals(COMPO) || node > 0)
+      HMMNode node = new HMMNode();
+      nodes.add(node);
+      Scanner scanner = new Scanner(line);
+      String next = scanner.next();
+
+      /*
+       * expect COMPO (optional) for average match emissions
+       * or a node number followed by node's match emissions
+       */
+      if (COMPO.equals(next) || nodeNo > 0)
       {
-        // stores match emission line in list
-        List<Double> matches = new ArrayList<>();
-        matches = fillList(matchReader, numberOfSymbols);
-        hmm.getNodes().get(node).setMatchEmissions(matches);
-        if (node > 0)
+        /*
+         * parse match emissions
+         */
+        double[] matches = parseDoubles(scanner, numberOfSymbols);
+        node.setMatchEmissions(matches);
+        if (!COMPO.equals(next))
         {
-          parseAnnotations(matchReader, node);
+          int resNo = parseAnnotations(scanner, node);
+          if (resNo == 0)
+          {
+            /*
+             * no MAP annotation provided, just number off from 0 (begin node)
+             */
+            resNo = nodeNo;
+          }
+          node.setResidueNumber(resNo);
         }
+        line = input.readLine();
       }
-      matchReader.close();
-      // stores insert emission line in list
-      line = input.readLine();
-      Scanner insertReader = new Scanner(line);
-      List<Double> inserts = new ArrayList<>();
-      inserts = fillList(insertReader, numberOfSymbols);
-      hmm.getNodes().get(node).setInsertEmissions(inserts);
-      insertReader.close();
-
-      // stores state transition line in list
+      scanner.close();
+
+      /*
+       * parse insert emissions
+       */
+      scanner = new Scanner(line);
+      double[] inserts = parseDoubles(scanner, numberOfSymbols);
+      node.setInsertEmissions(inserts);
+      scanner.close();
+
+      /*
+       * parse state transitions
+       */
       line = input.readLine();
-      Scanner transitionReader = new Scanner(line);
-      List<Double> transitions = new ArrayList<>();
-      transitions = fillList(transitionReader, NUMBER_OF_TRANSITIONS);
-      hmm.getNodes().get(node).setStateTransitions(transitions);
-      transitionReader.close();
+      scanner = new Scanner(line);
+      double[] transitions = parseDoubles(scanner,
+              NUMBER_OF_TRANSITIONS);
+      node.setStateTransitions(transitions);
+      scanner.close();
       line = input.readLine();
-      node++;
+
+      nodeNo++;
     }
 
+    hmm.setNodes(nodes);
   }
 
   /**
-   * Parses the annotations on the match emission line.
+   * Parses the annotations on the match emission line and add them to the node.
+   * (See p109 of the HMMER User Guide (V3.1b2) for the specification.) Returns
+   * the residue position that the node maps to, if provided, else zero.
    * 
    * @param scanner
-   *          The scanner which is processing match emission line.
-   * @param index
-   *          The index of node which is being scanned.
+   * @param node
    */
-  void parseAnnotations(Scanner scanner, int index)
+  int parseAnnotations(Scanner scanner, HMMNode node)
   {
-    if (hmm.mapIsActive())
-    {
-      int column;
-      column = scanner.nextInt();
-      hmm.getNodes().get(index).setAlignmentColumn(column - 1);
-      hmm.getNodeLookup().put(column - 1, index);
-    }
-    else
+    int mapTo = 0;
+
+    /*
+     * map from hmm node to sequence position, if provided
+     */
+    if (scanner.hasNext())
     {
-      scanner.next();
+      String value = scanner.next();
+      if (!"-".equals(value))
+      {
+        try
+        {
+          mapTo = Integer.parseInt(value);
+          node.setResidueNumber(mapTo);
+        } catch (NumberFormatException e)
+        {
+          // ignore
+        }
+      }
     }
 
+    /*
+     * hmm consensus residue if provided, else '-'
+     */
     if (scanner.hasNext())
     {
-      char consensusR;
-      consensusR = charValue(scanner.next());
-      hmm.getNodes().get(index).setConsensusResidue(consensusR);
+      node.setConsensusResidue(scanner.next().charAt(0));
     }
 
+    /*
+     * RF reference annotation, if provided, else '-'
+     */
     if (scanner.hasNext())
     {
-      char reference;
-      reference = charValue(scanner.next());
-      hmm.getNodes().get(index).setReferenceAnnotation(reference);
+      node.setReferenceAnnotation(scanner.next().charAt(0));
     }
 
+    /*
+     * 'm' for masked position, if provided, else '-'
+     */
     if (scanner.hasNext())
     {
-      char value;
-      value = charValue(scanner.next());
-      hmm.getNodes().get(index).setMaskValue(value);
+      node.setMaskValue(scanner.next().charAt(0));
     }
+
+    /*
+     * structure consensus symbol, if provided, else '-'
+     */
     if (scanner.hasNext())
     {
-      char consensusS;
-      consensusS = charValue(scanner.next());
-      hmm.getNodes().get(index).setConsensusStructure(consensusS);
+      node.setConsensusStructure(scanner.next().charAt(0));
     }
-  }
-
 
+    return mapTo;
+  }
 
   /**
-   * Fills a list of doubles based on an input line.
+   * Fills an array of doubles parsed from an input line
    * 
    * @param input
-   *          The scanner for the line containing the data to be transferred to
-   *          the list.
    * @param numberOfElements
-   *          The number of elements in the list to be filled.
-   * @return filled list Returns the list of doubles.
+   * @return
+   * @throws IOException
    */
-  static List<Double> fillList(Scanner input,
-          int numberOfElements)
+  static double[] parseDoubles(Scanner input,
+          int numberOfElements) throws IOException
   {
-    List<Double> list = new ArrayList<>();
+    double[] values = new double[numberOfElements];
     for (int i = 0; i < numberOfElements; i++)
     {
-
+      if (!input.hasNext())
+      {
+        throw new IOException("Incomplete data");
+      }
       String next = input.next();
-      if (next.contains("*")) // state transitions to or from delete states
-                              // occasionally have values of -infinity. These
-                              // values are represented by an * in the .hmm
-                              // file.
+      if (next.contains("*"))
       {
-        list.add(Double.NEGATIVE_INFINITY);
+        values[i] = Double.NEGATIVE_INFINITY;
       }
       else
       {
         double prob = Double.valueOf(next);
         prob = Math.pow(Math.E, -prob);
-        list.add(prob);
+        values[i] = prob;
       }
     }
-    return list;
-  }
-
-  
-  /**
-   * Writes a HMM to a file/
-   * 
-   * @param exportLocation
-   *          Filename, URL or Pasted String to write to.
-   * @throws FileNotFoundException
-   * @throws UnsupportedEncodingException
-   *
-   **/
-  
-  public void exportFile(String exportLocation) throws IOException
-  {
-    PrintWriter writer = new PrintWriter(exportLocation);
-    appendFileProperties(writer);
-    appendModel(writer);
-    writer.println("//");
-
-    writer.close();
-
-  }
-
-  /**
-   * Writes a HMM to a file/
-   * 
-   * @param exportLocation
-   *          Filename, URL or Pasted String to write to.
-   * @throws FileNotFoundException
-   * @throws UnsupportedEncodingException
-   *
-   **/
-
-  public void exportFile(File exportLocation) throws IOException
-  {
-    PrintWriter writer = new PrintWriter(exportLocation);
-    appendFileProperties(writer);
-    appendModel(writer);
-    writer.println("//");
-
-    writer.close();
-
+    return values;
   }
 
   /**
@@ -377,25 +426,19 @@ public class HMMFile extends AlignFile
    * @param columnSeparation
    *          The separation between subsequent data entries.
    * @param data
-   *          The list fo data to be added to the String.
+   *          The list of data to be added to the String.
    * @return
    */
   String addData(int initialColumnSeparation,
           int columnSeparation, List<String> data)
   {
-    String line = EMPTY;
-    int index = 0;
+    String line = "";
+    boolean first = true;
     for (String value : data)
     {
-      if (index == 0)
-      {
-        line += String.format("%" + initialColumnSeparation + "s", value);
-      }
-      else
-      {
-        line += String.format("%" + columnSeparation + "s", value);
-      }
-      index++;
+      int sep = first ? initialColumnSeparation : columnSeparation;
+      line += String.format("%" + sep + "s", value);
+      first = false;
     }
     return line;
   }
@@ -418,23 +461,22 @@ public class HMMFile extends AlignFile
   }
 
   /**
-   * Converts a list of doubles into a list of Strings, rounded to the nearest
-   * 5th decimal place.
+   * Converts an array of doubles into a list of Strings, rounded to the nearest
+   * 5th decimal place
    * 
-   * @param list
+   * @param doubles
    * @param noOfDecimals
    * @return
    */
-  List<String> doubleListToStringList(List<Double> list)
+  List<String> doublesToStringList(double[] doubles)
   {
     List<String> strList = new ArrayList<>();
-    for (double value : list)
+    for (double value : doubles)
     {
       String strValue;
       if (value > 0)
       {
         strValue = String.format("%.5f", value);
-
       }
       else if (value == -0.00000d)
       {
@@ -444,270 +486,231 @@ public class HMMFile extends AlignFile
       {
         strValue = "*";
       }
-
       strList.add(strValue);
     }
     return strList;
   }
 
   /**
-   * Converts a primitive array of Strings to a list of Strings.
+   * Appends model data in string format to the string builder
    * 
-   * @param array
-   * @return
+   * @param output
    */
-  List<String> stringArrayToStringList(String[] array)
+  void appendModelAsString(StringBuilder output)
   {
-    List<String> list = new ArrayList<>();
-    for (String value : array)
+    output.append(HMM).append("  ");
+    String charSymbols = hmm.getSymbols();
+    for (char c : charSymbols.toCharArray())
     {
-      list.add(value);
+      output.append(String.format("%9s", c));
     }
-
-    return list;
-  }
-
-  /**
-   * Appends the hidden Markov model data to the StringBuilder containing the
-   * output
-   * 
-   * @param file
-   *          The StringBuilder containing the output.
-   */
-  void appendModel(PrintWriter writer)
-  {
-    String symbolLine = "HMM";
-    List<Character> charSymbols = hmm.getSymbols();
-    List<String> strSymbols;
-    strSymbols = charListToStringList(charSymbols);
-    symbolLine += addData(11, 9, strSymbols);
-    writer.println(symbolLine);
-    writer.println(TRANSITIONTYPELINE);
+    output.append(NL).append(TRANSITIONTYPELINE);
 
     int length = hmm.getLength();
 
-    for (int node = 0; node <= length; node++)
+    for (int nodeNo = 0; nodeNo <= length; nodeNo++)
     {
-      String matchLine;
-      if (node == 0)
-      {
-        matchLine = String.format("%7s", "COMPO");
-      }
-      else
-      {
-        matchLine = String.format("%7s", node);
-      }
+      String matchLine = String.format("%7s",
+              nodeNo == 0 ? COMPO : Integer.toString(nodeNo));
 
-      List<String> strMatches;
-      List<Double> doubleMatches;
-      doubleMatches = convertListToLogSpace(
-              hmm.getNode(node).getMatchEmissions());
-      strMatches = doubleListToStringList(doubleMatches);
+      double[] doubleMatches = convertToLogSpace(
+              hmm.getNode(nodeNo).getMatchEmissions());
+      List<String> strMatches = doublesToStringList(doubleMatches);
       matchLine += addData(10, 9, strMatches);
 
-
-      if (node != 0)
+      if (nodeNo != 0)
       {
-        matchLine += SPACE + (hmm.getNodeAlignmentColumn(node) + 1);
-        matchLine += SPACE + hmm.getConsensusResidue(node);
-        matchLine += SPACE + hmm.getReferenceAnnotation(node);
+        matchLine += SPACE + (hmm.getNodeMapPosition(nodeNo));
+        matchLine += SPACE + hmm.getConsensusResidue(nodeNo);
+        matchLine += SPACE + hmm.getReferenceAnnotation(nodeNo);
         if (hmm.getFileHeader().contains("HMMER3/f"))
         {
-          matchLine += SPACE + hmm.getMaskedValue(node);
-          matchLine += SPACE + hmm.getConsensusStructure(node);
+          matchLine += SPACE + hmm.getMaskedValue(nodeNo);
+          matchLine += SPACE + hmm.getConsensusStructure(nodeNo);
         }
-
       }
 
-      writer.println(matchLine);
+      output.append(NL).append(matchLine);
       
-      String insertLine = EMPTY;
-      List<String> strInserts;
-      List<Double> doubleInserts;
-      doubleInserts = convertListToLogSpace(
-              hmm.getNode(node).getInsertEmissions());
-      strInserts = doubleListToStringList(doubleInserts);
+      String insertLine = "";
+
+      double[] doubleInserts = convertToLogSpace(
+              hmm.getNode(nodeNo).getInsertEmissions());
+      List<String> strInserts = doublesToStringList(doubleInserts);
       insertLine += addData(17, 9, strInserts);
 
-      writer.println(insertLine);
+      output.append(NL).append(insertLine);
 
-      String transitionLine = EMPTY;
-      List<String> strTransitions;
-      List<Double> doubleTransitions;
-      doubleTransitions = convertListToLogSpace(
-              hmm.getNode(node).getStateTransitions());
-      strTransitions = doubleListToStringList(doubleTransitions);
+      String transitionLine = "";
+      double[] doubleTransitions = convertToLogSpace(
+              hmm.getNode(nodeNo).getStateTransitions());
+      List<String> strTransitions = doublesToStringList(
+              doubleTransitions);
       transitionLine += addData(17, 9, strTransitions);
 
-      writer.println(transitionLine);
+      output.append(NL).append(transitionLine);
     }
   }
 
   /**
-   * Appends the hidden Markov model file properties to the StringBuilder
-   * containing the output
+   * Appends formatted HMM file properties to the string builder
    * 
-   * @param file
-   *          The StringBuilder containing the output.
+   * @param output
    */
-  void appendFileProperties(PrintWriter writer)
+  void appendProperties(StringBuilder output)
   {
-    String line;
-
-    writer.println(hmm.getFileHeader());
-    
-    line = String.format("%-5s %1s", "NAME", hmm.getName());
-    writer.println((line));
+    output.append(hmm.getFileHeader());
+
+    String format = "%n%-5s %1s";
+    appendProperty(output, format, NAME);
+    appendProperty(output, format, ACCESSION_NUMBER);
+    appendProperty(output, format, DESCRIPTION);
+    appendProperty(output, format, LENGTH);
+    appendProperty(output, format, MAX_LENGTH);
+    appendProperty(output, format, ALPHABET);
+    appendBooleanProperty(output, format, REFERENCE_ANNOTATION);
+    appendBooleanProperty(output, format, MASKED_VALUE);
+    appendBooleanProperty(output, format, CONSENSUS_RESIDUE);
+    appendBooleanProperty(output, format, CONSENSUS_STRUCTURE);
+    appendBooleanProperty(output, format, MAP);
+    appendProperty(output, format, DATE);
+    appendProperty(output, format, NUMBER_OF_SEQUENCES);
+    appendProperty(output, format, EFF_NUMBER_OF_SEQUENCES);
+    appendProperty(output, format, CHECK_SUM);
+    appendProperty(output, format, GATHERING_THRESHOLD);
+    appendProperty(output, format, TRUSTED_CUTOFF);
+    appendProperty(output, format, NOISE_CUTOFF);
 
-    if (hmm.getAccessionNumber() != null)
+    if (hmm.getMSV() != null)
     {
-    line = String.format("%-5s %1s", "ACC", hmm.getAccessionNumber());
-      writer.println((line));
-    }
+      format = "%n%-19s %18s";
+      output.append(String.format(format, "STATS LOCAL MSV", hmm.getMSV()));
 
-    if (hmm.getDescription() != null)
-    {
-    line = String.format("%-5s %1s", "DESC", hmm.getDescription());
-      writer.println((line));
-    }
-    line = String.format("%-5s %1s", "LENG", hmm.getLength());
-    writer.println((line));
+      output.append(String.format(format, "STATS LOCAL VITERBI",
+              hmm.getViterbi()));
 
-    if (hmm.getMaxInstanceLength() != null)
-    {
-    line = String.format("%-5s %1s", "MAXL", hmm.getMaxInstanceLength());
-      writer.println((line));
-    }
-    line = String.format("%-5s %1s", "ALPH", hmm.getAlphabetType());
-    writer.println((line));
-
-    boolean status;
-    String statusStr;
-
-    status = hmm.referenceAnnotationIsActive();
-    statusStr = HiddenMarkovModel.findStringFromBoolean(status);
-    line = String.format("%-5s %1s", "RF",
-            statusStr);
-    writer.println((line));
-
-    status = hmm.maskValueIsActive();
-    statusStr = HiddenMarkovModel.findStringFromBoolean(status);
-    line = String.format("%-5s %1s", "MM",
-            statusStr);
-    writer.println((line));
-    
-    status = hmm.consensusResidueIsActive();
-    statusStr = HiddenMarkovModel.findStringFromBoolean(status);
-    line = String.format("%-5s %1s", "CONS",
-            statusStr);
-    writer.println((line));
-
-    status = hmm.consensusStructureIsActive();
-    statusStr = HiddenMarkovModel.findStringFromBoolean(status);
-    line = String.format("%-5s %1s", "CS",
-            statusStr);
-    writer.println((line));
-
-    status = hmm.mapIsActive();
-    statusStr = HiddenMarkovModel.findStringFromBoolean(status);
-    line = String.format("%-5s %1s", "MAP",
-            statusStr);
-    writer.println((line));
-
-
-    if (hmm.getDate() != null)
-    {
-    line = String.format("%-5s %1s", "DATE", hmm.getDate());
-      writer.println((line));
-    }
-    if (hmm.getNumberOfSequences() != null)
-    {
-    line = String.format("%-5s %1s", "NSEQ", hmm.getNumberOfSequences());
-      writer.println((line));
-    }
-    if (hmm.getEffectiveNumberOfSequences() != null)
-    {
-    line = String.format("%-5s %1s", "EFFN",
-            hmm.getEffectiveNumberOfSequences());
-      writer.println((line));
-    }
-    if (hmm.getCheckSum() != null)
-    {
-    line = String.format("%-5s %1s", "CKSUM", hmm.getCheckSum());
-      writer.println((line));
-    }
-    if (hmm.getGatheringThreshold() != null)
-    {
-    line = String.format("%-5s %1s", "GA", hmm.getGatheringThreshold());
-      writer.println((line));
+      output.append(String.format(format, "STATS LOCAL FORWARD",
+              hmm.getForward()));
     }
+  }
 
-    if (hmm.getTrustedCutoff() != null)
-    {
-    line = String.format("%-5s %1s", "TC", hmm.getTrustedCutoff());
-      writer.println((line));
-    }
-    if (hmm.getNoiseCutoff() != null)
+  /**
+   * Appends 'yes' or 'no' for the given property, according to whether or not
+   * it is set in the HMM
+   * 
+   * @param output
+   * @param format
+   * @param propertyName
+   */
+  private void appendBooleanProperty(StringBuilder output, String format,
+          String propertyName)
+  {
+    boolean set = hmm.getBooleanProperty(propertyName);
+    output.append(String.format(format, propertyName,
+            set ? HiddenMarkovModel.YES : HiddenMarkovModel.NO));
+  }
+
+  /**
+   * Appends the value of the given property to the output, if not null
+   * 
+   * @param output
+   * @param format
+   * @param propertyName
+   */
+  private void appendProperty(StringBuilder output, String format,
+          String propertyName)
+  {
+    String value = hmm.getProperty(propertyName);
+    if (value != null)
     {
-    line = String.format("%-5s %1s", "NC", hmm.getNoiseCutoff());
-      writer.println((line));
+      output.append(String.format(format, propertyName, value));
     }
-    if (hmm.getMSV() != null)
+  }
+
+  @Override
+  public String print(SequenceI[] sequences, boolean jvsuffix)
+  {
+    if (sequences[0].getHMM() != null)
     {
-      line = String.format("%-19s %18s", "STATS LOCAL MSV", hmm.getMSV());
-      writer.println((line));
-
-      line = String.format("%-19s %18s", "STATS LOCAL VITERBI",
-              hmm.getViterbi());
-      writer.println((line));
-    
-      line = String.format("%-19s %18s", "STATS LOCAL FORWARD",
-              hmm.getForward());
-      writer.println((line));
+      hmm = sequences[0].getHMM();
     }
+    return print();
   }
 
-
   /**
-   * Returns the char value of a single lettered String.
+   * Prints the .hmm file to a String.
    * 
-   * @param string
    * @return
    */
-  char charValue(String string)
+  public String print()
   {
-    char character;
-    character = string.charAt(0);
-    return character;
-
+    StringBuilder output = new StringBuilder();
+    appendProperties(output);
+    output.append(NL);
+    appendModelAsString(output);
+    output.append(NL).append(TERMINATOR).append(NL);
+    return output.toString();
   }
 
-  @Override
-  public String print(SequenceI[] seqs, boolean jvsuffix)
+  /**
+   * Converts the probabilities contained in an array into log space
+   * 
+   * @param ds
+   */
+  double[] convertToLogSpace(double[] ds)
   {
+    double[] converted = new double[ds.length];
+    for (int i = 0; i < ds.length; i++)
+    {
+      double prob = ds[i];
+      double logProb = -1 * Math.log(prob);
 
-    return null;
+      converted[i] = logProb;
+    }
+    return converted;
   }
 
   /**
-   * Converts the probabilities contained in a list into log space.
-   * 
-   * @param list
+   * Returns the HMM sequence produced by reading a .hmm file.
    */
-  List<Double> convertListToLogSpace(List<Double> list)
+  @Override
+  public SequenceI[] getSeqsAsArray()
   {
+    SequenceI hmmSeq = hmm.getConsensusSequence();
+    SequenceI[] seq = new SequenceI[1];
+    seq[0] = hmmSeq;
+    return seq;
+  }
 
-    List<Double> convertedList = new ArrayList<>();
-    for (int i = 0; i < list.size(); i++)
-    {
-      double prob = list.get(i);
-      double logProb = -1 * Math.log(prob);
+  @Override
+  public void setNewlineString(String newLine)
+  {
+    NL = newLine;
+  }
 
-      convertedList.add(logProb);
-    }
-    return convertedList;
+  @Override
+  public void setExportSettings(AlignExportSettingsI exportSettings)
+  {
 
+  }
 
+  @Override
+  public void configureForView(AlignmentViewPanel viewpanel)
+  {
+
+  }
+
+  @Override
+  public boolean hasWarningMessage()
+  {
+    return false;
+  }
+
+  @Override
+  public String getWarningMessage()
+  {
+    return "warning message";
   }
+
 }