Merge branch 'develop' into update_212_Dec_merge_with_21125_chamges
[jalview.git] / src / jalview / util / MappingUtils.java
index 6294ca1..d60d4eb 100644 (file)
  */
 package jalview.util;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
 
 import jalview.analysis.AlignmentSorter;
 import jalview.api.AlignViewportI;
-import jalview.bin.Cache;
+import jalview.bin.Console;
 import jalview.commands.CommandI;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
@@ -41,6 +35,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.Mapping;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -48,6 +43,12 @@ import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 /**
  * Helper methods for manipulations involving sequence mappings.
  * 
@@ -80,7 +81,7 @@ public final class MappingUtils
       action = action.getUndoAction();
     }
     // TODO write this
-    Cache.log.error("MappingUtils.mapCutOrPaste not yet implemented");
+    Console.error("MappingUtils.mapCutOrPaste not yet implemented");
   }
 
   /**
@@ -364,47 +365,55 @@ public final class MappingUtils
       int startResiduePos = selected.findPosition(firstUngappedPos);
       int endResiduePos = selected.findPosition(lastUngappedPos);
 
-      for (AlignedCodonFrame acf : codonFrames)
+      for (SequenceI seq : mapTo.getAlignment().getSequences())
       {
-        for (SequenceI seq : mapTo.getAlignment().getSequences())
+        int mappedStartResidue = 0;
+        int mappedEndResidue = 0;
+        for (AlignedCodonFrame acf : codonFrames)
         {
-          SequenceI peptide = targetIsNucleotide ? selected : seq;
-          SequenceI cds = targetIsNucleotide ? seq : selected;
-          SequenceToSequenceMapping s2s = acf.getCoveringMapping(cds,
-                  peptide);
-          if (s2s == null)
-          {
-            continue;
-          }
-          int mappedStartResidue = 0;
-          int mappedEndResidue = 0;
-          List<AlignedCodonFrame> mapping = Arrays.asList(acf);
-          SearchResultsI sr = buildSearchResults(selected, startResiduePos,
-                  mapping);
-          for (SearchResultMatchI m : sr.getResults())
+          // rather than use acf.getCoveringMapping() we iterate through all
+          // mappings to make sure all CDS are selected for a protein
+          for (SequenceToSequenceMapping map : acf.getMappings())
           {
-            mappedStartResidue = m.getStart();
-            mappedEndResidue = m.getEnd();
-          }
-          sr = buildSearchResults(selected, endResiduePos, mapping);
-          for (SearchResultMatchI m : sr.getResults())
-          {
-            mappedStartResidue = Math.min(mappedStartResidue, m.getStart());
-            mappedEndResidue = Math.max(mappedEndResidue, m.getEnd());
-          }
+            if (map.covers(selected) && map.covers(seq))
+            {
+              /*
+               * Found a sequence mapping. Locate the start/end mapped residues.
+               */
+              List<AlignedCodonFrame> mapping = Arrays
+                      .asList(new AlignedCodonFrame[]
+                      { acf });
+              // locate start
+              SearchResultsI sr = buildSearchResults(selected,
+                      startResiduePos, mapping);
+              for (SearchResultMatchI m : sr.getResults())
+              {
+                mappedStartResidue = m.getStart();
+                mappedEndResidue = m.getEnd();
+              }
+              // locate end - allowing for adjustment of start range
+              sr = buildSearchResults(selected, endResiduePos, mapping);
+              for (SearchResultMatchI m : sr.getResults())
+              {
+                mappedStartResidue = Math.min(mappedStartResidue,
+                        m.getStart());
+                mappedEndResidue = Math.max(mappedEndResidue, m.getEnd());
+              }
 
-          /*
-           * Find the mapped aligned columns, save the range. Note findIndex
-           * returns a base 1 position, SequenceGroup uses base 0
-           */
-          int mappedStartCol = seq.findIndex(mappedStartResidue) - 1;
-          minStartCol = minStartCol == -1 ? mappedStartCol
-                  : Math.min(minStartCol, mappedStartCol);
-          int mappedEndCol = seq.findIndex(mappedEndResidue) - 1;
-          maxEndCol = maxEndCol == -1 ? mappedEndCol
-                  : Math.max(maxEndCol, mappedEndCol);
-          mappedGroup.addSequence(seq, false);
-          break;
+              /*
+               * Find the mapped aligned columns, save the range. Note findIndex
+               * returns a base 1 position, SequenceGroup uses base 0
+               */
+              int mappedStartCol = seq.findIndex(mappedStartResidue) - 1;
+              minStartCol = minStartCol == -1 ? mappedStartCol
+                      : Math.min(minStartCol, mappedStartCol);
+              int mappedEndCol = seq.findIndex(mappedEndResidue) - 1;
+              maxEndCol = maxEndCol == -1 ? mappedEndCol
+                      : Math.max(maxEndCol, mappedEndCol);
+              mappedGroup.addSequence(seq, false);
+              break;
+            }
+          }
         }
       }
     }
@@ -444,23 +453,23 @@ public final class MappingUtils
     {
       for (AlignedCodonFrame acf : mappings)
       {
-          for (SequenceI seq2 : mapTo.getSequences())
+        for (SequenceI seq2 : mapTo.getSequences())
+        {
+          /*
+           * the corresponding peptide / CDS is the one for which there is
+           * a complete ('covering') mapping to 'seq'
+           */
+          SequenceI peptide = mappingToNucleotide ? seq2 : seq;
+          SequenceI cds = mappingToNucleotide ? seq : seq2;
+          SequenceToSequenceMapping s2s = acf.getCoveringMapping(cds,
+                  peptide);
+          if (s2s != null)
           {
-            /*
-             * the corresponding peptide / CDS is the one for which there is
-             * a complete ('covering') mapping to 'seq'
-             */
-            SequenceI peptide = mappingToNucleotide ? seq2 : seq;
-            SequenceI cds = mappingToNucleotide ? seq : seq2;
-            SequenceToSequenceMapping s2s = acf.getCoveringMapping(cds,
-                    peptide);
-            if (s2s != null)
-            {
-              mappedOrder.add(seq2);
-              j++;
-              break;
-            }
+            mappedOrder.add(seq2);
+            j++;
+            break;
           }
+        }
       }
     }
 
@@ -522,7 +531,7 @@ public final class MappingUtils
 
     if (colsel == null)
     {
-      return; 
+      return; // mappedColumns;
     }
 
     char fromGapChar = mapFrom.getAlignment().getGapCharacter();
@@ -544,9 +553,10 @@ public final class MappingUtils
     while (regions.hasNext())
     {
       mapHiddenColumns(regions.next(), codonFrames, newHidden,
-              fromSequences, toSequences, fromGapChar);
+              fromSequences,
+              toSequences, fromGapChar);
     }
-    return; 
+    return; // mappedColumns;
   }
 
   /**
@@ -835,7 +845,7 @@ public final class MappingUtils
     {
       if (range.length % 2 != 0)
       {
-        Cache.log.error(
+        Console.error(
                 "Error unbalance start/end ranges: " + ranges.toString());
         return 0;
       }
@@ -991,7 +1001,7 @@ public final class MappingUtils
         /*
          * not coded for [start1, end1, start2, end2, ...]
          */
-        Cache.log.error(
+        Console.error(
                 "MappingUtils.removeEndPositions doesn't handle multiple  ranges");
         return;
       }
@@ -1002,7 +1012,7 @@ public final class MappingUtils
         /*
          * not coded for a reverse strand range (end < start)
          */
-        Cache.log.error(
+        Console.error(
                 "MappingUtils.removeEndPositions doesn't handle reverse strand");
         return;
       }
@@ -1018,27 +1028,83 @@ public final class MappingUtils
       }
     }
   }
+  /**
+   * Adds the given range to a list of ranges. If the new range just extends
+   * existing ranges, the current endpoint is updated instead.
+   * 
+   * @param range
+   * @param addTo
+   */
+  public static void addRange(int[] range, List<int[]> addTo)
+  {
+    /*
+     * list is empty - add to it!
+     */
+    if (addTo.size() == 0)
+    {
+      addTo.add(range);
+      return;
+    }
+  
+    int[] last = addTo.get(addTo.size() - 1);
+    boolean lastForward = last[1] >= last[0];
+    boolean newForward = range[1] >= range[0];
+  
+    /*
+     * contiguous range in the same direction - just update endpoint
+     */
+    if (lastForward == newForward && last[1] == range[0])
+    {
+      last[1] = range[1];
+      return;
+    }
+  
+    /*
+     * next range starts at +1 in forward sense - update endpoint
+     */
+    if (lastForward && newForward && range[0] == last[1] + 1)
+    {
+      last[1] = range[1];
+      return;
+    }
+  
+    /*
+     * next range starts at -1 in reverse sense - update endpoint
+     */
+    if (!lastForward && !newForward && range[0] == last[1] - 1)
+    {
+      last[1] = range[1];
+      return;
+    }
+  
+    /*
+     * just add the new range
+     */
+    addTo.add(range);
+  }
 
   /**
-   * Converts a list of [start, end] ranges to a single array of [start, end,
-   * start, end ...]
+   * Converts a list of {@code start-end} ranges to a single array of
+   * {@code start1, end1, start2, ... } ranges
    * 
    * @param ranges
    * @return
    */
-  public static int[] listToArray(List<int[]> ranges)
+  public static int[] rangeListToArray(List<int[]> ranges)
   {
-    int[] result = new int[ranges.size() * 2];
-    int i = 0;
-    for (int[] range : ranges)
+    int rangeCount = ranges.size();
+    int[] result = new int[rangeCount * 2];
+    int j = 0;
+    for (int i = 0; i < rangeCount; i++)
     {
-      result[i++] = range[0];
-      result[i++] = range[1];
+      int[] range = ranges.get(i);
+      result[j++] = range[0];
+      result[j++] = range[1];
     }
     return result;
   }
 
-  /**
+  /*
    * Returns the maximal start-end positions in the given (ordered) list of
    * ranges which is overlapped by the given begin-end range, or null if there
    * is no overlap.