JAL-845 refactor / typo fix
[jalview.git] / src / jalview / util / MappingUtils.java
index 466aeb8..9880ae7 100644 (file)
@@ -10,7 +10,9 @@ import jalview.commands.OrderCommand;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResults.Match;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -63,21 +65,29 @@ public final class MappingUtils
    * 
    * @param command
    * @param undo
-   * @param alignment
+   * @param mapTo
    * @param gapChar
    * @param mappings
    * @return
    */
   public static EditCommand mapEditCommand(EditCommand command,
-          boolean undo, final AlignmentI alignment, char gapChar,
+          boolean undo, final AlignmentI mapTo, char gapChar,
           Set<AlignedCodonFrame> mappings)
   {
     /*
+     * For now, only support mapping from protein edits to cDna
+     */
+    if (!mapTo.isNucleotide())
+    {
+      return null;
+    }
+
+    /*
      * Cache a copy of the target sequences so we can mimic successive edits on
      * them. This lets us compute mappings for all edits in the set.
      */
     Map<SequenceI, SequenceI> targetCopies = new HashMap<SequenceI, SequenceI>();
-    for (SequenceI seq : alignment.getSequences())
+    for (SequenceI seq : mapTo.getSequences())
     {
       SequenceI ds = seq.getDatasetSequence();
       if (ds != null)
@@ -88,12 +98,12 @@ public final class MappingUtils
         targetCopies.put(ds, copy);
       }
     }
-  
+
     /*
      * Compute 'source' sequences as they were before applying edits:
      */
     Map<SequenceI, SequenceI> originalSequences = command.priorState(undo);
-  
+
     EditCommand result = new EditCommand();
     Iterator<Edit> edits = command.getEditIterator(!undo);
     while (edits.hasNext())
@@ -102,15 +112,14 @@ public final class MappingUtils
       if (edit.getAction() == Action.CUT
               || edit.getAction() == Action.PASTE)
       {
-        mapCutOrPaste(edit, undo, alignment.getSequences(), result,
-                mappings);
+        mapCutOrPaste(edit, undo, mapTo.getSequences(), result, mappings);
       }
       else if (edit.getAction() == Action.INSERT_GAP
               || edit.getAction() == Action.DELETE_GAP)
       {
         mapInsertOrDelete(edit, undo, originalSequences,
-                alignment.getSequences(),
-                targetCopies, gapChar, result, mappings);
+                mapTo.getSequences(), targetCopies, gapChar, result,
+                mappings);
       }
     }
     return result.getSize() > 0 ? result : null;
@@ -139,7 +148,7 @@ public final class MappingUtils
           EditCommand result, Set<AlignedCodonFrame> mappings)
   {
     Action action = edit.getAction();
-  
+
     /*
      * Invert sense of action if an Undo.
      */
@@ -162,12 +171,12 @@ public final class MappingUtils
       }
       final SequenceI actedOn = originalSequences.get(ds);
       final int seqpos = actedOn.findPosition(editPos);
-  
+
       /*
        * Determine all mappings from this position to mapped sequences.
        */
       SearchResults sr = buildSearchResults(seq, seqpos, mappings);
-  
+
       if (!sr.isEmpty())
       {
         for (SequenceI targetSeq : targetSeqs)
@@ -184,7 +193,7 @@ public final class MappingUtils
           {
             final int ratio = 3; // TODO: compute this - how?
             final int mappedCount = count * ratio;
-  
+
             /*
              * Shift Delete start position left, as it acts on positions to its
              * right.
@@ -194,7 +203,7 @@ public final class MappingUtils
             Edit e = result.new Edit(action, new SequenceI[]
             { targetSeq }, mappedEditPos, mappedCount, gapChar);
             result.addEdit(e);
-  
+
             /*
              * and 'apply' the edit to our copy of its target sequence
              */
@@ -249,34 +258,37 @@ public final class MappingUtils
       {
         acf.markMappedRegion(seq, index, results);
       }
-      results.addResult(seq, index, index);
     }
     return results;
   }
 
   /**
-   * Returns a (possibly empty) SequenceGroup containing any sequences the
+   * Returns a (possibly empty) SequenceGroup containing any sequences in the
    * mapped viewport corresponding to the given group in the source viewport.
    * 
    * @param sg
-   * @param av
-   * @param mapped
+   * @param mapFrom
+   * @param mapTo
    * @return
    */
   public static SequenceGroup mapSequenceGroup(SequenceGroup sg,
-          AlignViewportI av, AlignViewportI mapped)
+          AlignViewportI mapFrom, AlignViewportI mapTo)
   {
     /*
-     * Map sequence selection. Note the SequenceGroup holds aligned sequences,
-     * the mappings hold dataset sequences.
+     * Note the SequenceGroup holds aligned sequences, the mappings hold dataset
+     * sequences.
      */
-    AlignedCodonFrame[] codonFrames = av.getAlignment()
+    boolean targetIsNucleotide = mapTo.isNucleotide();
+    AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
+    Set<AlignedCodonFrame> codonFrames = protein.getAlignment()
             .getCodonFrames();
 
     /*
-     * Copy group name, colours, but not sequences
+     * Copy group name, name colours, but not sequences or sequence colour
+     * scheme
      */
     SequenceGroup mappedGroup = new SequenceGroup(sg);
+    mappedGroup.cs = mapTo.getGlobalColourScheme();
     mappedGroup.clear();
     // TODO set width of mapped group
 
@@ -284,12 +296,13 @@ public final class MappingUtils
     {
       for (AlignedCodonFrame acf : codonFrames)
       {
-        SequenceI dnaSeq = acf.getDnaForAaSeq(selected);
-        if (dnaSeq != null)
+        SequenceI mappedSequence = targetIsNucleotide ? acf
+                .getDnaForAaSeq(selected) : acf.getAaForDnaSeq(selected);
+        if (mappedSequence != null)
         {
-          for (SequenceI seq : mapped.getAlignment().getSequences())
+          for (SequenceI seq : mapTo.getAlignment().getSequences())
           {
-            if (seq.getDatasetSequence() == dnaSeq)
+            if (seq.getDatasetSequence() == mappedSequence)
             {
               mappedGroup.addSequence(seq, false);
               break;
@@ -316,8 +329,7 @@ public final class MappingUtils
    * @return
    */
   public static CommandI mapOrderCommand(OrderCommand command,
-          boolean undo, AlignmentI mapTo,
-          Set<AlignedCodonFrame> mappings)
+          boolean undo, AlignmentI mapTo, Set<AlignedCodonFrame> mappings)
   {
     SequenceI[] sortOrder = command.getSequenceOrder(undo);
     List<SequenceI> mappedOrder = new ArrayList<SequenceI>();
@@ -326,12 +338,19 @@ public final class MappingUtils
     {
       for (AlignedCodonFrame acf : mappings)
       {
-        SequenceI dnaSeq = acf.getDnaForAaSeq(seq);
-        if (dnaSeq != null)
+        /*
+         * Try protein-to-Dna, failing that try dna-to-protein
+         */
+        SequenceI mappedSeq = acf.getDnaForAaSeq(seq);
+        if (mappedSeq == null)
+        {
+          mappedSeq = acf.getAaForDnaSeq(seq);
+        }
+        if (mappedSeq != null)
         {
           for (SequenceI seq2 : mapTo.getSequences())
           {
-            if (seq2.getDatasetSequence() == dnaSeq)
+            if (seq2.getDatasetSequence() == mappedSeq)
             {
               mappedOrder.add(seq2);
               j++;
@@ -366,8 +385,8 @@ public final class MappingUtils
     }
 
     /*
-     * Have to align the sequences before constructing the OrderCommand - which
-     * then realigns them?!?
+     * Have to sort the sequences before constructing the OrderCommand - which
+     * then resorts them?!?
      */
     final SequenceI[] mappedOrderArray = mappedOrder
             .toArray(new SequenceI[mappedOrder.size()]);
@@ -378,4 +397,94 @@ public final class MappingUtils
     return result;
   }
 
+  /**
+   * Returns a ColumnSelection in the 'mapTo' view which corresponds to the
+   * given selection in the 'mapFrom' view. We assume one is nucleotide, the
+   * other is protein (and holds the mappings from codons to protein residues).
+   * 
+   * @param colsel
+   * @param mapFrom
+   * @param mapTo
+   * @return
+   */
+  public static ColumnSelection mapColumnSelection(ColumnSelection colsel,
+          AlignViewportI mapFrom, AlignViewportI mapTo)
+  {
+    boolean targetIsNucleotide = mapTo.isNucleotide();
+    AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
+    Set<AlignedCodonFrame> codonFrames = protein.getAlignment()
+            .getCodonFrames();
+    ColumnSelection mappedColumns = new ColumnSelection();
+    char fromGapChar = mapFrom.getAlignment().getGapCharacter();
+
+    // FIXME allow for hidden columns
+
+    /*
+     * For each mapped column, find the range of columns that residues in that
+     * column map to.
+     */
+    for (Object obj : colsel.getSelected())
+    {
+      int col = ((Integer) obj).intValue();
+      int mappedToMin = Integer.MAX_VALUE;
+      int mappedToMax = Integer.MIN_VALUE;
+
+      /*
+       * For each sequence in the 'from' alignment
+       */
+      for (SequenceI fromSeq : mapFrom.getAlignment().getSequences())
+      {
+        /*
+         * Ignore gaps (unmapped anyway)
+         */
+        if (fromSeq.getCharAt(col) == fromGapChar)
+        {
+          continue;
+        }
+
+        /*
+         * Get the residue position and find the mapped position.
+         */
+        int residuePos = fromSeq.findPosition(col);
+        SearchResults sr = buildSearchResults(fromSeq, residuePos,
+                codonFrames);
+        for (Match m : sr.getResults())
+        {
+          int mappedStartResidue = m.getStart();
+          int mappedEndResidue = m.getEnd();
+          SequenceI mappedSeq = m.getSequence();
+
+          /*
+           * Locate the aligned sequence whose dataset is mappedSeq. TODO a
+           * datamodel that can do this efficiently.
+           */
+          for (SequenceI toSeq : mapTo.getAlignment().getSequences())
+          {
+            if (toSeq.getDatasetSequence() == mappedSeq)
+            {
+              int mappedStartCol = toSeq.findIndex(mappedStartResidue);
+              int mappedEndCol = toSeq.findIndex(mappedEndResidue);
+              mappedToMin = Math.min(mappedToMin, mappedStartCol);
+              mappedToMax = Math.max(mappedToMax, mappedEndCol);
+              // System.out.println(fromSeq.getName() + " mapped to cols "
+              // + mappedStartCol + ":" + mappedEndCol);
+              break;
+              // note: remove break if we ever want to map one to many sequences
+            }
+          }
+        }
+      }
+      /*
+       * Add the range of mapped columns to the mapped selection (converting
+       * base 1 to base 0). Note that this may include intron-only regions which
+       * lie between the start and end ranges of the selection.
+       */
+      for (int i = mappedToMin; i <= mappedToMax; i++)
+      {
+        mappedColumns.addElement(i - 1);
+      }
+    }
+    return mappedColumns;
+  }
+
 }