JAL-1619 refactoring in progress for Dna translation
[jalview.git] / src / jalview / analysis / Dna.java
index 0c020dd..cf6f83e 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.analysis;
 
+import jalview.bin.Cache;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
@@ -34,41 +35,49 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
+import jalview.util.DBRefUtils;
 import jalview.util.MapList;
 import jalview.util.ShiftList;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.List;
-import java.util.Vector;
-
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.Vector;
 
 public class Dna
 {
   /**
+   * Test whether codon positions cdp1 should align before, with, or after cdp2.
+   * Returns zero if all positions match (or either argument is null). Returns
+   * -1 if any position in the first codon precedes the corresponding position
+   * in the second codon. Else returns +1 (some position in the second codon
+   * precedes the corresponding position in the first).
+   *
+   * Note this is not necessarily symmetric, for example:
+   * <ul>
+   * <li>compareCodonPos([2,5,6], [3,4,5]) returns -1</li>
+   * <li>compareCodonPos([3,4,5], [2,5,6]) also returns -1</li>
+   * </ul>
    * 
    * @param cdp1
    * @param cdp2
-   * @return -1 if cdp1 aligns before cdp2, 0 if in the same column or cdp2 is
-   *         null, +1 if after cdp2
+   * @return
    */
-  private static int compare_codonpos(int[] cdp1, int[] cdp2)
+  public static int compareCodonPos(int[] cdp1, int[] cdp2)
   {
-    if (cdp2 == null
+    if (cdp1 == null
+            || cdp2 == null
             || (cdp1[0] == cdp2[0] && cdp1[1] == cdp2[1] && cdp1[2] == cdp2[2]))
     {
       return 0;
     }
     if (cdp1[0] < cdp2[0] || cdp1[1] < cdp2[1] || cdp1[2] < cdp2[2])
-     {
-      return -1; // one base in cdp1 precedes the corresponding base in the
+    {
+      // one base in cdp1 precedes the corresponding base in the other codon
+      return -1;
     }
-    // other codon
-    return 1; // one base in cdp1 appears after the corresponding base in the
-    // other codon.
+    // one base in cdp1 appears after the corresponding base in the other codon.
+    return 1;
   }
 
   /**
@@ -100,17 +109,17 @@ public class Dna
    *          destination dataset for translated sequences and mappings
    * @return
    */
-  public static AlignmentI CdnaTranslate(SequenceI[] selection,
+  public static AlignmentI cdnaTranslate(SequenceI[] selection,
           String[] seqstring, int viscontigs[], char gapCharacter,
           AlignmentAnnotation[] annotations, int aWidth, Alignment dataset)
   {
-    return CdnaTranslate(selection, seqstring, null, viscontigs,
-            gapCharacter, annotations, aWidth, dataset);
+    return cdnaTranslate(Arrays.asList(selection), seqstring, null,
+            viscontigs, gapCharacter, annotations, aWidth, dataset);
   }
 
   /**
    * 
-   * @param selection
+   * @param cdnaseqs
    * @param seqstring
    * @param product
    *          - array of DbRefEntry objects from which exon map in seqstring is
@@ -122,29 +131,33 @@ public class Dna
    * @param dataset
    * @return
    */
-  public static AlignmentI CdnaTranslate(SequenceI[] selection,
+  public static AlignmentI cdnaTranslate(List<SequenceI> cdnaseqs,
           String[] seqstring, DBRefEntry[] product, int viscontigs[],
           char gapCharacter, AlignmentAnnotation[] annotations, int aWidth,
           Alignment dataset)
   {
-    AlignedCodonFrame codons = new AlignedCodonFrame(aWidth); // stores hash of
-    // subsequent
-    // positions for
-    // each codon
-    // start position
-    // in alignment
-    int s, sSize = selection.length;
-    Vector pepseqs = new Vector();
+    AlignedCodonFrame acf = new AlignedCodonFrame(aWidth);
+
+    /*
+     * This array will be built up so that position i holds the codon positions
+     * e.g. [7, 9, 10] that match column i (base 0) in the aligned translation.
+     * Note this implies a contract that if two codons do not align exactly,
+     * their translated products must occupy different column positions.
+     */
+    int[][] alignedCodons = new int[aWidth][];
+
+    int s;
+    int sSize = cdnaseqs.size();
+    List<SequenceI> pepseqs = new ArrayList<SequenceI>();
     for (s = 0; s < sSize; s++)
     {
-      SequenceI newseq = translateCodingRegion(selection[s], seqstring[s],
-              viscontigs, codons, gapCharacter,
-              (product != null) ? product[s] : null, false); // possibly
-                                                             // anonymous
-      // product
+      SequenceI newseq = translateCodingRegion(cdnaseqs.get(s),
+              seqstring[s], viscontigs, acf, alignedCodons, gapCharacter,
+              false);
+
       if (newseq != null)
       {
-        pepseqs.addElement(newseq);
+        pepseqs.add(newseq);
         SequenceI ds = newseq;
         if (dataset != null)
         {
@@ -156,17 +169,15 @@ public class Dna
         }
       }
     }
-    if (codons.aaWidth == 0)
-    {
-      return null;
-    }
-    SequenceI[] newseqs = new SequenceI[pepseqs.size()];
-    pepseqs.copyInto(newseqs);
+
+    SequenceI[] newseqs = pepseqs.toArray(new SequenceI[pepseqs.size()]);
     AlignmentI al = new Alignment(newseqs);
-    al.padGaps(); // ensure we look aligned.
+    // ensure we look aligned.
+    al.padGaps();
+    // link the protein translation to the DNA dataset
     al.setDataset(dataset);
-    // translateAlignedAnnotations(annotations, al, codons);
-    al.addCodonFrame(codons);
+    translateAlignedAnnotations(annotations, al, acf, alignedCodons);
+    al.addCodonFrame(acf);
     return al;
   }
 
@@ -185,14 +196,13 @@ public class Dna
     for (int gd = 0; gd < selection.length; gd++)
     {
       SequenceI dna = selection[gd];
-      jalview.datamodel.DBRefEntry[] dnarefs = jalview.util.DBRefUtils
+      DBRefEntry[] dnarefs = DBRefUtils
               .selectRefs(dna.getDBRef(),
                       jalview.datamodel.DBRefSource.DNACODINGDBS);
       if (dnarefs != null)
       {
         // intersect with pep
-        // intersect with pep
-        Vector mappedrefs = new Vector();
+        List<DBRefEntry> mappedrefs = new ArrayList<DBRefEntry>();
         DBRefEntry[] refs = dna.getDBRef();
         for (int d = 0; d < refs.length; d++)
         {
@@ -200,11 +210,10 @@ public class Dna
                   && refs[d].getMap().getMap().getFromRatio() == 3
                   && refs[d].getMap().getMap().getToRatio() == 1)
           {
-            mappedrefs.addElement(refs[d]); // add translated protein maps
+            mappedrefs.add(refs[d]); // add translated protein maps
           }
         }
-        dnarefs = new DBRefEntry[mappedrefs.size()];
-        mappedrefs.copyInto(dnarefs);
+        dnarefs = mappedrefs.toArray(new DBRefEntry[mappedrefs.size()]);
         for (int d = 0; d < dnarefs.length; d++)
         {
           Mapping mp = dnarefs[d].getMap();
@@ -227,88 +236,16 @@ public class Dna
   }
 
   /**
-   * generate a set of translated protein products from annotated sequenceI
-   * 
-   * @param selection
-   * @param viscontigs
-   * @param gapCharacter
-   * @param dataset
-   *          destination dataset for translated sequences
-   * @param annotations
-   * @param aWidth
-   * @return
-   */
-  public static AlignmentI CdnaTranslate(SequenceI[] selection,
-          int viscontigs[], char gapCharacter, Alignment dataset)
-  {
-    int alwidth = 0;
-    Vector cdnasqs = new Vector();
-    Vector cdnasqi = new Vector();
-    Vector cdnaprod = new Vector();
-    for (int gd = 0; gd < selection.length; gd++)
-    {
-      SequenceI dna = selection[gd];
-      jalview.datamodel.DBRefEntry[] dnarefs = jalview.util.DBRefUtils
-              .selectRefs(dna.getDBRef(),
-                      jalview.datamodel.DBRefSource.DNACODINGDBS);
-      if (dnarefs != null)
-      {
-        // intersect with pep
-        Vector mappedrefs = new Vector();
-        DBRefEntry[] refs = dna.getDBRef();
-        for (int d = 0; d < refs.length; d++)
-        {
-          if (refs[d].getMap() != null && refs[d].getMap().getMap() != null
-                  && refs[d].getMap().getMap().getFromRatio() == 3
-                  && refs[d].getMap().getMap().getToRatio() == 1)
-          {
-            mappedrefs.addElement(refs[d]); // add translated protein maps
-          }
-        }
-        dnarefs = new DBRefEntry[mappedrefs.size()];
-        mappedrefs.copyInto(dnarefs);
-        for (int d = 0; d < dnarefs.length; d++)
-        {
-          Mapping mp = dnarefs[d].getMap();
-          StringBuffer sqstr = new StringBuffer();
-          if (mp != null)
-          {
-            Mapping intersect = mp.intersectVisContigs(viscontigs);
-            // generate seqstring for this sequence based on mapping
-
-            if (sqstr.length() > alwidth)
-            {
-              alwidth = sqstr.length();
-            }
-            cdnasqs.addElement(sqstr.toString());
-            cdnasqi.addElement(dna);
-            cdnaprod.addElement(intersect);
-          }
-        }
-      }
-      SequenceI[] cdna = new SequenceI[cdnasqs.size()];
-      DBRefEntry[] prods = new DBRefEntry[cdnaprod.size()];
-      String[] xons = new String[cdnasqs.size()];
-      cdnasqs.copyInto(xons);
-      cdnaprod.copyInto(prods);
-      cdnasqi.copyInto(cdna);
-      return CdnaTranslate(cdna, xons, prods, viscontigs, gapCharacter,
-              null, alwidth, dataset);
-    }
-    return null;
-  }
-
-  /**
    * Translate na alignment annotations onto translated amino acid alignment al
    * using codon mapping codons
    * 
    * @param annotations
    * @param al
-   * @param codons
+   * @param acf
    */
-  public static void translateAlignedAnnotations(
+  protected static void translateAlignedAnnotations(
           AlignmentAnnotation[] annotations, AlignmentI al,
-          AlignedCodonFrame codons)
+          AlignedCodonFrame acf, int[][] codons)
   {
     // Can only do this for columns with consecutive codons, or where
     // annotation is sequence associated.
@@ -329,7 +266,7 @@ public class Dna
           continue;
         }
 
-        int aSize = codons.getaaWidth(); // aa alignment width.
+        int aSize = acf.getaaWidth(); // aa alignment width.
         Annotation[] anots = (annotation.annotations == null) ? null
                 : new Annotation[aSize];
         if (anots != null)
@@ -337,10 +274,10 @@ public class Dna
           for (int a = 0; a < aSize; a++)
           {
             // process through codon map.
-            if (a < codons.codons.length && codons.codons[a] != null
-                    && codons.codons[a][0] == (codons.codons[a][2] - 2))
+            if (a < codons.length && codons[a] != null
+                    && codons[a][0] == (codons[a][2] - 2))
             {
-              anots[a] = getCodonAnnotation(codons.codons[a],
+              anots[a] = getCodonAnnotation(codons[a],
                       annotation.annotations);
             }
           }
@@ -364,7 +301,7 @@ public class Dna
         final SequenceI seqRef = annotation.sequenceRef;
         if (seqRef != null)
         {
-          SequenceI aaSeq = codons.getAaForDnaSeq(seqRef);
+          SequenceI aaSeq = acf.getAaForDnaSeq(seqRef);
           if (aaSeq != null)
           {
             // aa.compactAnnotationArray(); // throw away alignment annotation
@@ -438,42 +375,18 @@ public class Dna
    *          ORF read in some global alignment reference frame
    * @param viscontigs
    *          mapping from global reference frame to visible seqstring ORF read
-   * @param codons
-   *          Definition of global ORF alignment reference frame
-   * @param gapCharacter
-   * @return sequence ready to be added to alignment.
-   * @deprecated Use
-   *             {@link #translateCodingRegion(SequenceI,String,int[],AlignedCodonFrame,char,DBRefEntry,boolean)}
-   *             instead
-   */
-  @Deprecated
-  public static SequenceI translateCodingRegion(SequenceI selection,
-          String seqstring, int[] viscontigs, AlignedCodonFrame codons,
-          char gapCharacter, DBRefEntry product)
-  {
-    return translateCodingRegion(selection, seqstring, viscontigs, codons,
-            gapCharacter, product, false);
-  }
-
-  /**
-   * Translate a na sequence
-   * 
-   * @param selection
-   *          sequence displayed under viscontigs visible columns
-   * @param seqstring
-   *          ORF read in some global alignment reference frame
-   * @param viscontigs
-   *          mapping from global reference frame to visible seqstring ORF read
-   * @param codons
+   * @param acf
    *          Definition of global ORF alignment reference frame
+   * @param alignedCodons
    * @param gapCharacter
    * @param starForStop
    *          when true stop codons will translate as '*', otherwise as 'X'
    * @return sequence ready to be added to alignment.
    */
-  public static SequenceI translateCodingRegion(SequenceI selection,
-          String seqstring, int[] viscontigs, AlignedCodonFrame codons,
-          char gapCharacter, DBRefEntry product, final boolean starForStop)
+  protected static SequenceI translateCodingRegion(SequenceI selection,
+          String seqstring, int[] viscontigs, AlignedCodonFrame acf,
+          int[][] alignedCodons, char gapCharacter,
+          final boolean starForStop)
   {
     List<int[]> skip = new ArrayList<int[]>();
     int skipint[] = null;
@@ -498,7 +411,7 @@ public class Dna
 
     // allocate a roughly sized buffer for the protein sequence
     StringBuilder protein = new StringBuilder(seqstring.length() / 2);
-    String seq = seqstring.replace('U', 'T');
+    String seq = seqstring.replace('U', 'T').replace('u', 'T');
     char codon[] = new char[3];
     int cdp[] = new int[3], rf = 0, lastnpos = 0, nend;
     int aspos = 0;
@@ -517,9 +430,10 @@ public class Dna
          */
         String aa = ResidueProperties.codonTranslate(new String(codon));
         rf = 0;
+        final String gapString = String.valueOf(gapCharacter);
         if (aa == null)
         {
-          aa = String.valueOf(gapCharacter);
+          aa = gapString;
           if (skipint == null)
           {
             skipint = new int[]
@@ -624,48 +538,77 @@ public class Dna
           }
           resSize++;
         }
-        // insert/delete gaps prior to this codon - if necessary
+        // insert gaps prior to this codon - if necessary
         boolean findpos = true;
         while (findpos)
         {
-          // first ensure that the codons array is long enough.
-          codons.checkCodonFrameWidth(aspos);
-          // now check to see if we place the aa at the current aspos in the
-          // protein alignment
-          switch (Dna.compare_codonpos(cdp, codons.codons[aspos]))
+          // expand the codons array if necessary
+          alignedCodons = checkCodonFrameWidth(alignedCodons, aspos);
+
+          /*
+           * Compare this codon's base positions with those currently aligned to
+           * this column in the translation.
+           */
+          final int compareCodonPos = Dna.compareCodonPos(cdp,
+                  alignedCodons[aspos]);
+          // debug
+          // System.out.println(seq + "/" + aa + " codons: "
+          // + Arrays.deepToString(alignedCodons));
+          // System.out
+          // .println(("Compare " + Arrays.toString(cdp) + " at pos "
+          // + aspos + " with "
+          // + Arrays.toString(alignedCodons[aspos]) + " got " +
+          // compareCodonPos));
+          // end debug
+          switch (compareCodonPos)
           {
           case -1:
-            codons.insertAAGap(aspos, gapCharacter);
+
+            /*
+             * This codon should precede the mapped positions - need to insert a
+             * gap in all prior sequences.
+             */
+            acf.insertAAGap(aspos, gapCharacter);
             findpos = false;
             break;
+
           case +1:
-            // this aa appears after the aligned codons at aspos, so prefix it
-            // with a gap
-            aa = "" + gapCharacter + aa;
+
+            /*
+             * This codon belongs after the aligned codons at aspos. Prefix it
+             * with a gap and try the next position.
+             */
+            aa = gapString + aa;
             aspos++;
-            // if (aspos >= codons.aaWidth)
-            // codons.aaWidth = aspos + 1;
-            break; // check the next position for alignment
+            break;
+
           case 0:
-            // codon aligns at aspos position.
+
+            /*
+             * Exact match - codon 'belongs' at this translated position.
+             */
             findpos = false;
           }
         }
-        // codon aligns with all other sequence residues found at aspos
         protein.append(aa);
         lastnpos = npos;
-        if (codons.codons[aspos] == null)
+        if (alignedCodons[aspos] == null)
         {
           // mark this column as aligning to this aligned reading frame
-          codons.codons[aspos] = new int[]
+          alignedCodons[aspos] = new int[]
           { cdp[0], cdp[1], cdp[2] };
         }
-        if (aspos >= codons.aaWidth)
+        else if (!Arrays.equals(alignedCodons[aspos], cdp))
+        {
+          throw new IllegalStateException("Tried to coalign "
+                  + Arrays.asList(alignedCodons[aspos], cdp));
+        }
+        if (aspos >= acf.aaWidth)
         {
           // update maximum alignment width
           // (we can do this without calling checkCodonFrameWidth because it was
           // already done above)
-          codons.setAaWidth(aspos);
+          acf.setAaWidth(aspos);
         }
         // ready for next translated reading frame alignment position (if any)
         aspos++;
@@ -677,15 +620,14 @@ public class Dna
               protein.toString());
       if (rf != 0)
       {
-        if (jalview.bin.Cache.log != null)
+        final String errMsg = "trimming contigs for incomplete terminal codon.";
+        if (Cache.log != null)
         {
-          jalview.bin.Cache.log
-                  .debug("trimming contigs for incomplete terminal codon.");
+          Cache.log.debug(errMsg);
         }
         else
         {
-          System.err
-                  .println("trimming contigs for incomplete terminal codon.");
+          System.err.println(errMsg);
         }
         // map and trim contigs to ORF region
         vc = scontigs.length - 1;
@@ -756,27 +698,13 @@ public class Dna
         MapList map = new MapList(scontigs, new int[]
         { 1, resSize }, 3, 1);
 
-        // update newseq as if it was generated as mapping from product
-
-        if (product != null)
-        {
-          newseq.setName(product.getSource() + "|"
-                  + product.getAccessionId());
-          if (product.getMap() != null)
-          {
-            // Mapping mp = product.getMap();
-            // newseq.setStart(mp.getPosition(scontigs[0]));
-            // newseq.setEnd(mp
-            // .getPosition(scontigs[scontigs.length - 1]));
-          }
-        }
         transferCodedFeatures(selection, newseq, map, null, null);
         SequenceI rseq = newseq.deriveSequence(); // construct a dataset
         // sequence for our new
         // peptide, regardless.
         // store a mapping (this actually stores a mapping between the dataset
         // sequences for the two sequences
-        codons.addMap(selection, rseq, map);
+        acf.addMap(selection, rseq, map);
         return rseq;
       }
     }
@@ -786,6 +714,32 @@ public class Dna
   }
 
   /**
+   * Check the codons array is big enough to accommodate the given position, if
+   * not resize it.
+   * 
+   * @param alignedCodons
+   * @param aspos
+   * @return the resized array (or the original if no resize needed)
+   */
+  protected static int[][] checkCodonFrameWidth(int[][] alignedCodons,
+          int aspos)
+  {
+    // TODO why not codons.length < aspos ?
+    // should codons expand if length is 2 or 3 and aslen==2 ?
+    if (alignedCodons.length <= aspos + 1)
+    {
+      // probably never have to do this ?
+      int[][] c = new int[alignedCodons.length + 10][];
+      for (int i = 0; i < alignedCodons.length; i++)
+      {
+        c[i] = alignedCodons[i];
+      }
+      return c;
+    }
+    return alignedCodons;
+  }
+
+  /**
    * Given a peptide newly translated from a dna sequence, copy over and set any
    * features on the peptide from the DNA. If featureTypes is null, all features
    * on the dna sequence are searched (rather than just the displayed ones), and