Merge branch 'merge/develop_JAL-3725' into develop
authorJim Procter <j.procter@dundee.ac.uk>
Fri, 28 Jan 2022 17:08:35 +0000 (17:08 +0000)
committerJim Procter <j.procter@dundee.ac.uk>
Fri, 28 Jan 2022 17:08:35 +0000 (17:08 +0000)
 Conflicts:
src/jalview/gui/PopupMenu.java

src/jalview/datamodel/MappedFeatures.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/gui/PopupMenu.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/util/MapList.java
src/jalview/util/MappingUtils.java
src/jalview/ws/dbsources/EmblXmlSource.java
test/jalview/datamodel/SequenceFeatureTest.java
test/jalview/util/MapListTest.java
test/jalview/util/MappingUtilsTest.java
test/jalview/ws/dbsources/EmblXmlSourceTest.java

index f145d93..ca6db1b 100644 (file)
@@ -296,8 +296,8 @@ public class MappedFeatures
   public int[] getMappedPositions(int begin, int end)
   {
     MapList map = mapping.getMap();
-    return mapping.to == featureSequence ? map.locateInFrom(begin, end)
-            : map.locateInTo(begin, end);
+    return mapping.to == featureSequence ? map.getOverlapsInFrom(begin, end)
+            : map.getOverlapsInTo(begin, end);
   }
 
   /**
index 6eeba2f..bbf1b45 100755 (executable)
@@ -612,10 +612,9 @@ public class SequenceFeature implements FeatureLocationI
     String consequence = "";
     if (mf != null)
     {
-      int[] beginRange = mf.getMappedPositions(begin, begin);
-      int[] endRange = mf.getMappedPositions(end, end);
-      int from = beginRange[0];
-      int to = endRange[endRange.length - 1];
+      int[] localRange = mf.getMappedPositions(begin, end);
+      int from = localRange[0];
+      int to = localRange[localRange.length - 1];
       String s = mf.isFromCds() ? "Peptide Location" : "Coding location";
       sb.append(String.format(ROW_DATA, s, seqName, from == to ? from
               : from + (isContactFeature() ? ":" : "-") + to));
index 6cd763b..7b284a9 100644 (file)
@@ -839,15 +839,14 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       /*
        * show local rather than linked feature coordinates
        */
-      int[] beginRange = mf.getMappedPositions(start, start);
-      int[] endRange = mf.getMappedPositions(end, end);
-      if (beginRange == null || endRange == null)
+      int[] localRange = mf.getMappedPositions(start, end);
+      if (localRange == null)
       {
         // e.g. variant extending to stop codon so not mappable
         return;
       }
-      start = beginRange[0];
-      end = endRange[endRange.length - 1];
+      start = localRange[0];
+      end = localRange[localRange.length - 1];
     }
     StringBuilder desc = new StringBuilder();
     desc.append(sf.getType()).append(" ").append(String.valueOf(start));
index 62b723d..9ffdf21 100644 (file)
@@ -209,12 +209,27 @@ public class SequenceAnnotationReport
      * if this is a virtual features, convert begin/end to the
      * coordinates of the sequence it is mapped to
      */
-    int[] beginRange = null;
-    int[] endRange = null;
+    int[] beginRange = null; // feature start in local coordinates
+    int[] endRange = null; // feature end in local coordinates
     if (mf != null)
     {
-      beginRange = mf.getMappedPositions(begin, begin);
-      endRange = mf.getMappedPositions(end, end);
+      if (feature.isContactFeature())
+      {
+        /*
+         * map start and end points individually
+         */
+        beginRange = mf.getMappedPositions(begin, begin);
+        endRange = begin == end ? beginRange
+                : mf.getMappedPositions(end, end);
+      }
+      else
+      {
+        /*
+         * map the feature extent
+         */
+        beginRange = mf.getMappedPositions(begin, end);
+        endRange = beginRange;
+      }
       if (beginRange == null || endRange == null)
       {
         // something went wrong
index 3555e52..8efe42b 100644 (file)
@@ -25,6 +25,8 @@ import java.util.Arrays;
 import java.util.BitSet;
 import java.util.List;
 
+import jalview.bin.Cache;
+
 /**
  * A simple way of bijectively mapping a non-contiguous linear range to another
  * non-contiguous linear range.
@@ -307,7 +309,7 @@ public class MapList
       if (range.length != 2)
       {
         // throw new IllegalArgumentException(range);
-        System.err.println("Invalid format for fromRange "
+        Cache.log.error("Invalid format for fromRange "
                 + Arrays.toString(range) + " may cause errors");
       }
       fromLowest = Math.min(fromLowest, Math.min(range[0], range[1]));
@@ -321,7 +323,7 @@ public class MapList
       if (range.length != 2)
       {
         // throw new IllegalArgumentException(range);
-        System.err.println("Invalid format for toRange "
+        Cache.log.error("Invalid format for toRange "
                 + Arrays.toString(range) + " may cause errors");
       }
       toLowest = Math.min(toLowest, Math.min(range[0], range[1]));
@@ -467,7 +469,8 @@ public class MapList
     int mp[][] = new int[to - from + 2][];
     for (int i = 0; i < mp.length; i++)
     {
-      int[] m = shift(i + from, shiftTo, sourceRatio, shiftFrom, targetRatio);
+      int[] m = shift(i + from, shiftTo, sourceRatio, shiftFrom,
+              targetRatio);
       if (m != null)
       {
         if (i == 0)
@@ -1144,10 +1147,11 @@ public class MapList
   }
 
   /**
-   * Returns the [start1, end1, start2, end2, ...] positions in the 'from' range
-   * that map to positions between {@code start} and {@code end} in the 'to'
-   * range. Note that for a reverse strand mapping this will return ranges with
-   * end < start. Returns null if no mapped positions are found in start-end.
+   * <<<<<<< HEAD Returns the [start1, end1, start2, end2, ...] positions in the
+   * 'from' range that map to positions between {@code start} and {@code end} in
+   * the 'to' range. Note that for a reverse strand mapping this will return
+   * ranges with end < start. Returns null if no mapped positions are found in
+   * start-end.
    * 
    * @param start
    * @param end
@@ -1155,8 +1159,8 @@ public class MapList
    */
   public int[] locateInFrom(int start, int end)
   {
-    return mapPositions(start, end, toShifts, fromShifts,
-            toRatio, fromRatio);
+    return mapPositions(start, end, toShifts, fromShifts, toRatio,
+            fromRatio);
   }
 
   /**
@@ -1171,8 +1175,8 @@ public class MapList
    */
   public int[] locateInTo(int start, int end)
   {
-    return mapPositions(start, end, fromShifts, toShifts,
-            fromRatio, toRatio);
+    return mapPositions(start, end, fromShifts, toShifts, fromRatio,
+            toRatio);
   }
 
   /**
@@ -1250,7 +1254,8 @@ public class MapList
    * @return
    */
   protected final static BitSet getMappedOffsetsForPositions(int start,
-          int end, List<int[]> sourceRange, int sourceWordLength, int targetWordLength)
+          int end, List<int[]> sourceRange, int sourceWordLength,
+          int targetWordLength)
   {
     BitSet overlaps = new BitSet();
     int offset = 0;
@@ -1419,4 +1424,36 @@ public class MapList
 
     return added;
   }
+
+  /*
+   * Returns the [start, end...] positions in the range mapped from, that are
+   * mapped to by part or all of the given begin-end of the range mapped to.
+   * Returns null if begin-end does not overlap any position mapped to.
+   * 
+   * @param begin
+   * @param end
+   * @return
+   */
+  public int[] getOverlapsInFrom(final int begin, final int end)
+  {
+    int[] overlaps = MappingUtils.findOverlap(toShifts, begin, end);
+
+    return overlaps == null ? null : locateInFrom(overlaps[0], overlaps[1]);
+  }
+
+  /**
+   * Returns the [start, end...] positions in the range mapped to, that are
+   * mapped to by part or all of the given begin-end of the range mapped from.
+   * Returns null if begin-end does not overlap any position mapped from.
+   * 
+   * @param begin
+   * @param end
+   * @return
+   */
+  public int[] getOverlapsInTo(final int begin, final int end)
+  {
+    int[] overlaps = MappingUtils.findOverlap(fromShifts, begin, end);
+
+    return overlaps == null ? null : locateInTo(overlaps[0], overlaps[1]);
+  }
 }
index c8b5190..590e1c5 100644 (file)
@@ -835,7 +835,7 @@ public final class MappingUtils
     {
       if (range.length % 2 != 0)
       {
-        System.err.println(
+        Cache.log.error(
                 "Error unbalance start/end ranges: " + ranges.toString());
         return 0;
       }
@@ -991,7 +991,7 @@ public final class MappingUtils
         /*
          * not coded for [start1, end1, start2, end2, ...]
          */
-        System.err.println(
+        Cache.log.error(
                 "MappingUtils.removeEndPositions doesn't handle multiple  ranges");
         return;
       }
@@ -1002,7 +1002,7 @@ public final class MappingUtils
         /*
          * not coded for a reverse strand range (end < start)
          */
-        System.err.println(
+        Cache.log.error(
                 "MappingUtils.removeEndPositions doesn't handle reverse strand");
         return;
       }
@@ -1039,4 +1039,66 @@ public final class MappingUtils
     }
     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.
+   * 
+   * <pre>
+   * Examples:
+   *   if ranges is {[4, 8], [10, 12], [16, 19]}
+   * then
+   *   findOverlap(ranges, 1, 20) == [4, 19]
+   *   findOverlap(ranges, 6, 11) == [6, 11]
+   *   findOverlap(ranges, 9, 15) == [10, 12]
+   *   findOverlap(ranges, 13, 15) == null
+   * </pre>
+   * 
+   * @param ranges
+   * @param begin
+   * @param end
+   * @return
+   */
+  protected static int[] findOverlap(List<int[]> ranges, final int begin,
+          final int end)
+  {
+    boolean foundStart = false;
+    int from = 0;
+    int to = 0;
+
+    /*
+     * traverse the ranges to find the first position (if any) >= begin,
+     * and the last position (if any) <= end
+     */
+    for (int[] range : ranges)
+    {
+      if (!foundStart)
+      {
+        if (range[0] >= begin)
+        {
+          /*
+           * first range that starts with, or follows, begin
+           */
+          foundStart = true;
+          from = Math.max(range[0], begin);
+        }
+        else if (range[1] >= begin)
+        {
+          /*
+           * first range that contains begin
+           */
+          foundStart = true;
+          from = begin;
+        }
+      }
+
+      if (range[0] <= end)
+      {
+        to = Math.min(end, range[1]);
+      }
+    }
+
+    return foundStart && to >= from ? new int[] { from, to } : null;
+  }
 }
index c2d661b..06cbb13 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.ws.dbsources;
 
-import java.util.Locale;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
@@ -30,6 +28,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -65,12 +64,6 @@ import jalview.xml.binding.embl.EntryType.Feature.Qualifier;
 import jalview.xml.binding.embl.ROOT;
 import jalview.xml.binding.embl.XrefType;
 
-/**
- * Provides XML binding and parsing of EMBL or EMBLCDS records retrieved from
- * (e.g.) {@code https://www.ebi.ac.uk/ena/data/view/x53828&display=xml}.
- * 
- * @deprecated endpoint withdrawn August 2020 (JAL-3692), use EmblFlatfileSource
- */
 public abstract class EmblXmlSource extends EbiFileRetrievedProxy
 {
   private static final Regex ACCESSION_REGEX = new Regex("^[A-Z]+[0-9]+");
@@ -104,8 +97,8 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
     try
     {
       reply = dbFetch.fetchDataAsFile(
-              emprefx.toLowerCase(Locale.ROOT) + ":" + query.trim(), "display=xml",
-              "xml");
+              emprefx.toLowerCase(Locale.ROOT) + ":" + query.trim(),
+              "display=xml", "xml");
     } catch (Exception e)
     {
       stopQuery();
@@ -455,9 +448,8 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
         else
         {
           // final product length truncation check
-          int[] cdsRanges = adjustForProteinLength(translationLength,
-                  exons);
-          dnaToProteinMapping = new Mapping(product, cdsRanges,
+          int[] exons2 = adjustForProteinLength(translationLength, exons);
+          dnaToProteinMapping = new Mapping(product, exons2,
                   new int[]
                   { 1, translationLength }, 3, 1);
           if (product != null)
@@ -759,8 +751,7 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
 
   /**
    * Truncates (if necessary) the exon intervals to match 3 times the length of
-   * the protein; also accepts 3 bases longer (for stop codon not included in
-   * protein)
+   * the protein (including truncation for stop codon included in exon)
    * 
    * @param proteinLength
    * @param exon
@@ -777,11 +768,9 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
     int exonLength = MappingUtils.getLength(Arrays.asList(exon));
 
     /*
-     * if exon length matches protein, or is shorter, or longer by the 
-     * length of a stop codon (3 bases), then leave it unchanged
+     * if exon length matches protein, or is shorter, then leave it unchanged
      */
-    if (expectedCdsLength >= exonLength
-            || expectedCdsLength == exonLength - 3)
+    if (expectedCdsLength >= exonLength)
     {
       return exon;
     }
index f8479a3..bd5e9ac 100644 (file)
@@ -48,8 +48,8 @@ public class SequenceFeatureTest
   @Test(groups = { "Functional" })
   public void testCopyConstructors()
   {
-    SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33,
-            12.5f, "group");
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33, 12.5f,
+            "group");
     sf1.setValue("STRAND", "+");
     sf1.setValue("Note", "Testing");
     Integer count = Integer.valueOf(7);
@@ -83,8 +83,8 @@ public class SequenceFeatureTest
     /*
      * copy constructor modifying type/begin/end/group/score
      */
-    SequenceFeature sf4 = new SequenceFeature(sf1, "Disulfide bond", 12,
-            15, "group3", -9.1f);
+    SequenceFeature sf4 = new SequenceFeature(sf1, "Disulfide bond", 12, 15,
+            "group3", -9.1f);
     assertEquals("Disulfide bond", sf4.getType());
     assertTrue(sf4.isContactFeature());
     assertEquals("desc", sf4.getDescription());
@@ -104,8 +104,8 @@ public class SequenceFeatureTest
   @Test(groups = { "Functional" })
   public void testGetValue()
   {
-    SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33,
-            12.5f, "group");
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33, 12.5f,
+            "group");
     sf1.setValue("STRAND", "+");
     assertEquals("+", sf1.getValue("STRAND"));
     assertNull(sf1.getValue("strand")); // case-sensitive
@@ -137,15 +137,15 @@ public class SequenceFeatureTest
   @Test(groups = { "Functional" })
   public void testEqualsAndHashCode()
   {
-    SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33,
-            12.5f, "group");
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33, 12.5f,
+            "group");
     sf1.setValue("ID", "id");
     sf1.setValue("Name", "name");
     sf1.setValue("Parent", "parent");
     sf1.setStrand("+");
     sf1.setPhase("1");
-    SequenceFeature sf2 = new SequenceFeature("type", "desc", 22, 33,
-            12.5f, "group");
+    SequenceFeature sf2 = new SequenceFeature("type", "desc", 22, 33, 12.5f,
+            "group");
     sf2.setValue("ID", "id");
     sf2.setValue("Name", "name");
     sf2.setValue("Parent", "parent");
@@ -158,10 +158,10 @@ public class SequenceFeatureTest
     assertEquals(sf1.hashCode(), sf2.hashCode());
 
     // changing type breaks equals:
-    SequenceFeature sf3 = new SequenceFeature("type", "desc", 22, 33,
-            12.5f, "group");
-    SequenceFeature sf4 = new SequenceFeature("Type", "desc", 22, 33,
-            12.5f, "group");
+    SequenceFeature sf3 = new SequenceFeature("type", "desc", 22, 33, 12.5f,
+            "group");
+    SequenceFeature sf4 = new SequenceFeature("Type", "desc", 22, 33, 12.5f,
+            "group");
     assertFalse(sf3.equals(sf4));
 
     // changing description breaks equals:
@@ -195,7 +195,8 @@ public class SequenceFeatureTest
 
     // changing start position breaks equals:
     int restorei = sf2.getBegin();
-    sf2 = new SequenceFeature(sf2, 21, sf2.getEnd(), sf2.getFeatureGroup(), sf2.getScore());
+    sf2 = new SequenceFeature(sf2, 21, sf2.getEnd(), sf2.getFeatureGroup(),
+            sf2.getScore());
     assertFalse(sf1.equals(sf2));
     sf2 = new SequenceFeature(sf2, restorei, sf2.getEnd(),
             sf2.getFeatureGroup(), sf2.getScore());
@@ -210,9 +211,11 @@ public class SequenceFeatureTest
 
     // changing feature group breaks equals:
     restores = sf2.getFeatureGroup();
-    sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(), "Group", sf2.getScore());
+    sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(), "Group",
+            sf2.getScore());
     assertFalse(sf1.equals(sf2));
-    sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(), restores, sf2.getScore());
+    sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(), restores,
+            sf2.getScore());
 
     // changing ID breaks equals:
     restores = (String) sf2.getValue("ID");
@@ -285,7 +288,8 @@ public class SequenceFeatureTest
     String seqName = seq.getName();
 
     // single locus, no group, no score
-    SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22, null);
+    SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22,
+            null);
     String expected = "<br><table><tr><td>Location</td><td>TestSeq</td><td>22</td></tr>"
             + "<tr><td>Type</td><td>variant</td><td></td></tr>"
             + "<tr><td>Description</td><td>G,C</td><td></td></tr></table>";
@@ -299,8 +303,7 @@ public class SequenceFeatureTest
             + "<tr><td>Description</td><td>a description</td><td></td></tr></table>";
     assertEquals(expected, sf.getDetailsReport(seqName, null));
 
-    sf = new SequenceFeature("variant", "G,C", 22, 33,
-            12.5f, "group");
+    sf = new SequenceFeature("variant", "G,C", 22, 33, 12.5f, "group");
     sf.setValue("Parent", "ENSG001");
     sf.setValue("Child", "ENSP002");
     expected = "<br><table><tr><td>Location</td><td>TestSeq</td><td>22-33</td></tr>"
@@ -351,9 +354,23 @@ public class SequenceFeatureTest
             + "<tr><td>Type</td><td>variant</td><td></td></tr>"
             + "<tr><td>Description</td><td>G,C</td><td></td></tr>"
             + "<tr><td>Consequence</td><td><i>Translated by Jalview</i></td><td>p.Leu9Phe</td></tr>"
-            + "<tr><td>alleles</td><td></td><td>G,C</td></tr>"
+            + "<tr><td>alleles</td><td></td><td>G,C</td></tr>" 
             + "</table>";
 
     assertEquals(expected, sf.getDetailsReport(seq.getName(), mf));
+    
+
+    /*
+     * exon feature extending beyond mapped range; mapped location should be
+     * restricted to peptide mapped range limit i.e. 10-13
+     */
+    SequenceFeature sf2 = new SequenceFeature("exon", "exon 1", 109, 230, null);
+    features.add(sf2);
+    expected = "<br><table><tr><td>Location</td><td>Cds</td><td>109-230</td></tr>"
+            + "<tr><td>Peptide Location</td><td>TestSeq</td><td>10-13</td></tr>"
+            + "<tr><td>Type</td><td>exon</td><td></td></tr>"
+            + "<tr><td>Description</td><td>exon 1</td><td></td></tr>"
+            + "</table>";
+    assertEquals(expected, sf2.getDetailsReport(seq.getName(), mf));
   }
 }
index 3f1f7f3..538dab8 100644 (file)
@@ -290,7 +290,7 @@ public class MapListTest
      * no overlap
      */
     assertNull(ml.locateInFrom(0, 0));
-    
+
   }
 
   /**
@@ -312,7 +312,7 @@ public class MapListTest
     assertEquals("[10, 10, 12, 12, 14, 14]",
             Arrays.toString(ml.locateInFrom(3, 3)));
     assertEquals("[16, 18]", Arrays.toString(ml.locateInFrom(4, 4)));
-    
+
     /*
      * codons at 11-16, 21-26, 31-36 mapped to peptide positions 1, 3-4, 6-8
      */
@@ -337,6 +337,86 @@ public class MapListTest
   }
 
   /**
+   * Tests for method that locates the overlap of the ranges in the 'from' map
+   * for given range in the 'to' map
+   */
+  @Test(groups = { "Functional" })
+  public void testGetOverlapsInFrom_withIntrons()
+  {
+    /*
+     * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
+     * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
+     */
+    int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
+    int[] protein = { 11, 14 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+
+    assertEquals("[2, 3, 5, 5]",
+            Arrays.toString(ml.getOverlapsInFrom(11, 11)));
+    assertEquals("[2, 3, 5, 7, 9, 9]",
+            Arrays.toString(ml.getOverlapsInFrom(11, 12)));
+    // out of range 5' :
+    assertEquals("[2, 3, 5, 7, 9, 9]",
+            Arrays.toString(ml.getOverlapsInFrom(8, 12)));
+    // out of range 3' :
+    assertEquals("[10, 10, 12, 12, 14, 14, 16, 18]",
+            Arrays.toString(ml.getOverlapsInFrom(13, 16)));
+    // out of range both :
+    assertEquals("[2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18]",
+            Arrays.toString(ml.getOverlapsInFrom(1, 16)));
+    // no overlap:
+    assertNull(ml.getOverlapsInFrom(20, 25));
+  }
+
+  /**
+   * Tests for method that locates the overlap of the ranges in the 'to' map for
+   * given range in the 'from' map
+   */
+  @Test(groups = { "Functional" })
+  public void testGetOverlapsInTo_withIntrons()
+  {
+    /*
+     * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [17, 18, 19] i.e.
+     * 2-3, 5-7, 9-10, 12-12, 14-14, 17-19
+     */
+    int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 17, 19 };
+    /*
+     * Mapped proteins at positions 1, 3, 4, 6 in the sequence
+     */
+    int[] protein = { 1, 1, 3, 4, 6, 6 };
+    MapList ml = new MapList(codons, protein, 3, 1);
+
+    /*
+     * Can't map from an unmapped position
+     */
+    assertNull(ml.getOverlapsInTo(1, 1));
+    assertNull(ml.getOverlapsInTo(4, 4));
+    assertNull(ml.getOverlapsInTo(15, 16));
+
+    /*
+     * nor from a range that includes no mapped position (exon)
+     */
+    assertNull(ml.getOverlapsInTo(15, 16));
+
+    // end of codon 1 maps to first peptide
+    assertEquals("[1, 1]", Arrays.toString(ml.getOverlapsInTo(2, 2)));
+    // end of codon 1 and start of codon 2 maps to first 2 peptides
+    assertEquals("[1, 1, 3, 3]", Arrays.toString(ml.getOverlapsInTo(3, 7)));
+
+    // range overlaps 5' end of dna:
+    assertEquals("[1, 1, 3, 3]", Arrays.toString(ml.getOverlapsInTo(1, 6)));
+    assertEquals("[1, 1, 3, 3]", Arrays.toString(ml.getOverlapsInTo(1, 8)));
+
+    // range overlaps 3' end of dna:
+    assertEquals("[6, 6]", Arrays.toString(ml.getOverlapsInTo(17, 24)));
+    assertEquals("[6, 6]", Arrays.toString(ml.getOverlapsInTo(16, 24)));
+
+    // dna positions 8, 11 are intron but include end of exon 2 and start of
+    // exon 3
+    assertEquals("[3, 4]", Arrays.toString(ml.getOverlapsInTo(8, 11)));
+  }
+
+  /**
    * Tests for method that locates ranges in the 'to' map for given range in the
    * 'from' map.
    */
@@ -376,7 +456,7 @@ public class MapListTest
      */
     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 13)));
     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(-1, 2)));
-    
+
     /*
      * no overlap
      */
@@ -422,7 +502,7 @@ public class MapListTest
     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 2)));
     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 4)));
     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 4)));
-    
+
     /*
      * no overlap
      */
@@ -895,7 +975,7 @@ public class MapListTest
     toRanges = compound.getToRanges();
     assertEquals(2, toRanges.size());
     assertArrayEquals(new int[] { 931, 901 }, toRanges.get(0));
-    assertArrayEquals(new int[] { 600, 582}, toRanges.get(1));
+    assertArrayEquals(new int[] { 600, 582 }, toRanges.get(1));
 
     /*
      * 1:1 plus 1:3 should result in 1:3
@@ -1085,7 +1165,7 @@ public class MapListTest
      * no overlap
      */
     assertNull(ml.locateInTo(0, 0));
-    
+
     /*
      * partial overlap
      */
@@ -1101,7 +1181,7 @@ public class MapListTest
     ml = new MapList(gene, cds, 1, 1);
     assertEquals("[13203, 13204]",
             Arrays.toString(ml.locateInTo(13468, 13468)));
-    
+
     /*
      * gene to protein
      * the base at 13468 is in the codon for 4401N and also 4402R
@@ -1220,7 +1300,7 @@ public class MapListTest
   public void testAddOffsetPositions()
   {
     List<int[]> mapped = new ArrayList<>();
-    int[] range = new int[] {10, 20};
+    int[] range = new int[] { 10, 20 };
     BitSet offsets = new BitSet();
 
     MapList.addOffsetPositions(mapped, 0, range, offsets);
@@ -1262,7 +1342,7 @@ public class MapListTest
     assertArrayEquals(new int[] { 14, 13 }, mapped.get(1));
     assertArrayEquals(new int[] { 10, 10 }, mapped.get(2));
   }
-  
+
   @Test(groups = { "Functional" })
   public void testGetPositionsForOffsets()
   {
@@ -1270,28 +1350,28 @@ public class MapListTest
     BitSet offsets = new BitSet();
     List<int[]> mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertTrue(mapped.isEmpty()); // no ranges and no offsets!
-    
+
     offsets.set(5, 1000);
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertTrue(mapped.isEmpty()); // no ranges
-    
+
     /*
      * one range with overlap of offsets
      */
-    ranges.add(new int[] {15, 25});
+    ranges.add(new int[] { 15, 25 });
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertEquals(1, mapped.size());
-    assertArrayEquals(new int[] {20,  25}, mapped.get(0));
-    
+    assertArrayEquals(new int[] { 20, 25 }, mapped.get(0));
+
     /*
      * two ranges
      */
-    ranges.add(new int[] {300, 320});
+    ranges.add(new int[] { 300, 320 });
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertEquals(2, mapped.size());
-    assertArrayEquals(new int[] {20,  25}, mapped.get(0));
-    assertArrayEquals(new int[] {300, 320}, mapped.get(1));
-    
+    assertArrayEquals(new int[] { 20, 25 }, mapped.get(0));
+    assertArrayEquals(new int[] { 300, 320 }, mapped.get(1));
+
     /*
      * boundary case - right end of first range overlaps
      */
@@ -1299,45 +1379,45 @@ public class MapListTest
     offsets.set(10);
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertEquals(1, mapped.size());
-    assertArrayEquals(new int[] {25,  25}, mapped.get(0));
-    
+    assertArrayEquals(new int[] { 25, 25 }, mapped.get(0));
+
     /*
      * boundary case - left end of second range overlaps
      */
     offsets.set(11);
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertEquals(2, mapped.size());
-    assertArrayEquals(new int[] {25,  25}, mapped.get(0));
-    assertArrayEquals(new int[] {300, 300}, mapped.get(1));
-    
+    assertArrayEquals(new int[] { 25, 25 }, mapped.get(0));
+    assertArrayEquals(new int[] { 300, 300 }, mapped.get(1));
+
     /*
      * offsets into a circular range are reported in
      * the order in which they are traversed
      */
     ranges.clear();
-    ranges.add(new int[] {100, 150});
-    ranges.add(new int[] {60, 80});
+    ranges.add(new int[] { 100, 150 });
+    ranges.add(new int[] { 60, 80 });
     offsets.clear();
     offsets.set(45, 55); // sets bits 45 to 54
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertEquals(2, mapped.size());
-    assertArrayEquals(new int[] {145, 150}, mapped.get(0)); // offsets 45-50
-    assertArrayEquals(new int[] {60, 63}, mapped.get(1)); // offsets 51-54
+    assertArrayEquals(new int[] { 145, 150 }, mapped.get(0)); // offsets 45-50
+    assertArrayEquals(new int[] { 60, 63 }, mapped.get(1)); // offsets 51-54
 
     /*
      * reverse range overlap is reported with start < end
      */
     ranges.clear();
-    ranges.add(new int[] {4321, 4000});
+    ranges.add(new int[] { 4321, 4000 });
     offsets.clear();
     offsets.set(20, 22); // sets bits 20 and 21
     offsets.set(30);
     mapped = MapList.getPositionsForOffsets(ranges, offsets);
     assertEquals(2, mapped.size());
-    assertArrayEquals(new int[] {4301, 4300}, mapped.get(0));
-    assertArrayEquals(new int[] {4291, 4291}, mapped.get(1));
+    assertArrayEquals(new int[] { 4301, 4300 }, mapped.get(0));
+    assertArrayEquals(new int[] { 4291, 4291 }, mapped.get(1));
   }
-  
+
   @Test(groups = { "Functional" })
   public void testGetMappedOffsetsForPositions()
   {
@@ -1345,9 +1425,10 @@ public class MapListTest
      * start by verifying the examples in the method's Javadoc!
      */
     List<int[]> ranges = new ArrayList<>();
-    ranges.add(new int[] {10, 20});
-    ranges.add(new int[] {31, 40});
-    BitSet overlaps = MapList.getMappedOffsetsForPositions(1, 9, ranges, 1, 1);
+    ranges.add(new int[] { 10, 20 });
+    ranges.add(new int[] { 31, 40 });
+    BitSet overlaps = MapList.getMappedOffsetsForPositions(1, 9, ranges, 1,
+            1);
     assertTrue(overlaps.isEmpty());
     overlaps = MapList.getMappedOffsetsForPositions(1, 11, ranges, 1, 1);
     assertEquals(2, overlaps.cardinality());
@@ -1355,75 +1436,80 @@ public class MapListTest
     assertTrue(overlaps.get(1));
     overlaps = MapList.getMappedOffsetsForPositions(15, 35, ranges, 1, 1);
     assertEquals(11, overlaps.cardinality());
-    for (int i = 5 ; i <= 11 ; i++)
+    for (int i = 5; i <= 11; i++)
     {
       assertTrue(overlaps.get(i));
     }
-    
+
     ranges.clear();
-    ranges.add(new int[] {1, 200});
+    ranges.add(new int[] { 1, 200 });
     overlaps = MapList.getMappedOffsetsForPositions(9, 9, ranges, 1, 3);
     assertEquals(3, overlaps.cardinality());
     assertTrue(overlaps.get(24));
     assertTrue(overlaps.get(25));
     assertTrue(overlaps.get(26));
-    
+
     ranges.clear();
-    ranges.add(new int[] {101, 150});
-    ranges.add(new int[] {171, 180});
+    ranges.add(new int[] { 101, 150 });
+    ranges.add(new int[] { 171, 180 });
     overlaps = MapList.getMappedOffsetsForPositions(101, 102, ranges, 3, 1);
     assertEquals(1, overlaps.cardinality());
     assertTrue(overlaps.get(0));
     overlaps = MapList.getMappedOffsetsForPositions(150, 171, ranges, 3, 1);
     assertEquals(1, overlaps.cardinality());
     assertTrue(overlaps.get(16));
-    
+
     ranges.clear();
-    ranges.add(new int[] {101, 150});
-    ranges.add(new int[] {21, 30});
+    ranges.add(new int[] { 101, 150 });
+    ranges.add(new int[] { 21, 30 });
     overlaps = MapList.getMappedOffsetsForPositions(24, 40, ranges, 3, 1);
     assertEquals(3, overlaps.cardinality());
     assertTrue(overlaps.get(17));
     assertTrue(overlaps.get(18));
     assertTrue(overlaps.get(19));
-    
+
     /*
      * reverse range 1:1 (e.g. reverse strand gene to transcript)
      */
     ranges.clear();
-    ranges.add(new int[] {20, 10});
+    ranges.add(new int[] { 20, 10 });
     overlaps = MapList.getMappedOffsetsForPositions(12, 13, ranges, 1, 1);
     assertEquals(2, overlaps.cardinality());
     assertTrue(overlaps.get(7));
     assertTrue(overlaps.get(8));
-    
+
     /*
      * reverse range 3:1 (e.g. reverse strand gene to peptide)
      * from EMBL:J03321 to P0CE20
      */
     ranges.clear();
-    ranges.add(new int[] {1480, 488});
-    overlaps = MapList.getMappedOffsetsForPositions(1460, 1460, ranges, 3, 1);
+    ranges.add(new int[] { 1480, 488 });
+    overlaps = MapList.getMappedOffsetsForPositions(1460, 1460, ranges, 3,
+            1);
     // 1460 is the end of the 7th codon
     assertEquals(1, overlaps.cardinality());
     assertTrue(overlaps.get(6));
     // add one base (part codon)
-    overlaps = MapList.getMappedOffsetsForPositions(1459, 1460, ranges, 3, 1);
+    overlaps = MapList.getMappedOffsetsForPositions(1459, 1460, ranges, 3,
+            1);
     assertEquals(2, overlaps.cardinality());
     assertTrue(overlaps.get(6));
     assertTrue(overlaps.get(7));
     // add second base (part codon)
-    overlaps = MapList.getMappedOffsetsForPositions(1458, 1460, ranges, 3, 1);
+    overlaps = MapList.getMappedOffsetsForPositions(1458, 1460, ranges, 3,
+            1);
     assertEquals(2, overlaps.cardinality());
     assertTrue(overlaps.get(6));
     assertTrue(overlaps.get(7));
     // add third base (whole codon)
-    overlaps = MapList.getMappedOffsetsForPositions(1457, 1460, ranges, 3, 1);
+    overlaps = MapList.getMappedOffsetsForPositions(1457, 1460, ranges, 3,
+            1);
     assertEquals(2, overlaps.cardinality());
     assertTrue(overlaps.get(6));
     assertTrue(overlaps.get(7));
     // add one more base (part codon)
-    overlaps = MapList.getMappedOffsetsForPositions(1456, 1460, ranges, 3, 1);
+    overlaps = MapList.getMappedOffsetsForPositions(1456, 1460, ranges, 3,
+            1);
     assertEquals(3, overlaps.cardinality());
     assertTrue(overlaps.get(6));
     assertTrue(overlaps.get(7));
index bd81d30..1fdfb16 100644 (file)
@@ -22,9 +22,10 @@ package jalview.util;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
-import static org.testng.AssertJUnit.fail;
+import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
 
 import java.awt.Color;
 import java.io.IOException;
@@ -1340,32 +1341,21 @@ public class MappingUtilsTest
   }
 
   @Test(groups = "Functional")
-  public void testListToArray()
+  public void testFindOverlap()
   {
     List<int[]> ranges = new ArrayList<>();
-
-    int[] result = MappingUtils.rangeListToArray(ranges);
-    assertEquals(result.length, 0);
-    ranges.add(new int[] { 24, 12 });
-    result = MappingUtils.rangeListToArray(ranges);
-    assertEquals(result.length, 2);
-    assertEquals(result[0], 24);
-    assertEquals(result[1], 12);
-    ranges.add(new int[] { -7, 30 });
-    result = MappingUtils.rangeListToArray(ranges);
-    assertEquals(result.length, 4);
-    assertEquals(result[0], 24);
-    assertEquals(result[1], 12);
-    assertEquals(result[2], -7);
-    assertEquals(result[3], 30);
-    try
-    {
-      MappingUtils.rangeListToArray(null);
-      fail("Expected exception");
-    } catch (NullPointerException e)
-    {
-      // expected
-    }
+    ranges.add(new int[] { 4, 8 });
+    ranges.add(new int[] { 10, 12 });
+    ranges.add(new int[] { 16, 19 });
+
+    int[] overlap = MappingUtils.findOverlap(ranges, 5, 13);
+    assertArrayEquals(overlap, new int[] { 5, 12 });
+    overlap = MappingUtils.findOverlap(ranges, -100, 100);
+    assertArrayEquals(overlap, new int[] { 4, 19 });
+    overlap = MappingUtils.findOverlap(ranges, 7, 17);
+    assertArrayEquals(overlap, new int[] { 7, 17 });
+    overlap = MappingUtils.findOverlap(ranges, 13, 15);
+    assertNull(overlap);
   }
   
   /**
index a0991e5..236e0a5 100644 (file)
@@ -26,6 +26,14 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.DBRefSource;
@@ -36,14 +44,6 @@ import jalview.xml.binding.embl.EntryType.Feature;
 import jalview.xml.binding.embl.EntryType.Feature.Qualifier;
 import jalview.xml.binding.embl.XrefType;
 
-import java.io.ByteArrayInputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
 public class EmblXmlSourceTest
 {
 
@@ -352,11 +352,12 @@ public class EmblXmlSourceTest
     // exact length match:
     assertSame(exons, EmblXmlSource.adjustForProteinLength(6, exons));
 
-    // match if we assume exons include stop codon not in protein:
-    assertSame(exons, EmblXmlSource.adjustForProteinLength(5, exons));
+    // truncate last exon by 3bp (e.g. stop codon)
+    int[] truncated = EmblXmlSource.adjustForProteinLength(5, exons);
+    assertEquals("[11, 15, 21, 25, 31, 35]", Arrays.toString(truncated));
 
     // truncate last exon by 6bp
-    int[] truncated = EmblXmlSource.adjustForProteinLength(4, exons);
+    truncated = EmblXmlSource.adjustForProteinLength(4, exons);
     assertEquals("[11, 15, 21, 25, 31, 32]", Arrays.toString(truncated));
 
     // remove last exon and truncate preceding by 1bp